diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 3859d4a79..35b7ea427 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -313,6 +313,7 @@

Settings

uTorrent (Beta) Deluge (Beta) QBitTorrent + Real-Debrid
@@ -442,6 +443,13 @@

Settings

+
+
+ + + Key can be acquired from https://real-debrid.com/apitoken +
+
@@ -2423,29 +2431,34 @@

Settings

if ($("#torrent_downloader_blackhole").is(":checked")) { - $("#transmission_options,#utorrent_options,#deluge_options,#qbittorrent_options").hide(); + $("#transmission_options,#utorrent_options,#deluge_options,#qbittorrent_options,#realdebrid_options").hide(); $("#torrent_blackhole_options").show(); } if ($("#torrent_downloader_transmission").is(":checked")) { - $("#torrent_blackhole_options,#utorrent_options,#deluge_options,#qbittorrent_options").hide(); + $("#torrent_blackhole_options,#utorrent_options,#deluge_options,#qbittorrent_options,#realdebrid_options").hide(); $("#transmission_options").show(); } if ($("#torrent_downloader_utorrent").is(":checked")) { - $("#torrent_blackhole_options,#transmission_options,#deluge_options,#qbittorrent_options").hide(); + $("#torrent_blackhole_options,#transmission_options,#deluge_options,#qbittorrent_options,#realdebrid_options").hide(); $("#utorrent_options").show(); } if ($("#torrent_downloader_qbittorrent").is(":checked")) { - $("#torrent_blackhole_options,#transmission_options,#utorrent_options,#deluge_options").hide(); + $("#torrent_blackhole_options,#transmission_options,#utorrent_options,#deluge_options,#realdebrid_options").hide(); $("#qbittorrent_options").show(); } if ($("#torrent_downloader_deluge").is(":checked")) { - $("#torrent_blackhole_options,#transmission_options,#utorrent_options,#qbittorrent_options").hide(); + $("#torrent_blackhole_options,#transmission_options,#utorrent_options,#qbittorrent_options,#realdebrid_options").hide(); $("#deluge_options").show(); } + if ($("#torrent_downloader_realdebrid").is(":checked")) + { + $("#torrent_blackhole_options,#transmission_options,#utorrent_options,#qbittorrent_options,#deluge_options").hide(); + $("#realdebrid_options").show(); + } $('input[type=radio]').change(function(){ if ($("#preferred_bitrate").is(":checked")) @@ -2482,23 +2495,27 @@

Settings

} if ($("#torrent_downloader_blackhole").is(":checked")) { - $("#transmission_options,#utorrent_options,#deluge_options,#qbittorrent_options").fadeOut("fast", function() { $("#torrent_blackhole_options").fadeIn() }); + $("#transmission_options,#utorrent_options,#deluge_options,#qbittorrent_options,#realdebrid_options").fadeOut("fast", function() { $("#torrent_blackhole_options").fadeIn() }); } if ($("#torrent_downloader_transmission").is(":checked")) { - $("#torrent_blackhole_options,#utorrent_options,#deluge_options,#qbittorrent_options").fadeOut("fast", function() { $("#transmission_options").fadeIn() }); + $("#torrent_blackhole_options,#utorrent_options,#deluge_options,#qbittorrent_options,#realdebrid_options").fadeOut("fast", function() { $("#transmission_options").fadeIn() }); } if ($("#torrent_downloader_utorrent").is(":checked")) { - $("#torrent_blackhole_options,#transmission_options,#deluge_options,#qbittorrent_options").fadeOut("fast", function() { $("#utorrent_options").fadeIn() }); + $("#torrent_blackhole_options,#transmission_options,#deluge_options,#qbittorrent_options,#realdebrid_options").fadeOut("fast", function() { $("#utorrent_options").fadeIn() }); } if ($("#torrent_downloader_qbittorrent").is(":checked")) { - $("#torrent_blackhole_options,#transmission_options,#utorrent_options,#deluge_options").fadeOut("fast", function() { $("#qbittorrent_options").fadeIn() }); + $("#torrent_blackhole_options,#transmission_options,#utorrent_options,#deluge_options,#realdebrid_options").fadeOut("fast", function() { $("#qbittorrent_options").fadeIn() }); } if ($("#torrent_downloader_deluge").is(":checked")) { - $("#torrent_blackhole_options,#utorrent_options,#transmission_options,#qbittorrent_options").fadeOut("fast", function() { $("#deluge_options").fadeIn() }); + $("#torrent_blackhole_options,#utorrent_options,#transmission_options,#qbittorrent_options,#realdebrid_options").fadeOut("fast", function() { $("#deluge_options").fadeIn() }); + } + if ($("#torrent_downloader_realdebrid").is(":checked")) + { + $("#torrent_blackhole_options,#utorrent_options,#transmission_options,#qbittorrent_options,#deluge_options").fadeOut("fast", function() { $("#realdebrid_options").fadeIn() }); } }); diff --git a/headphones/config.py b/headphones/config.py index 83c734153..3ccac944d 100644 --- a/headphones/config.py +++ b/headphones/config.py @@ -306,6 +306,7 @@ def __repr__(self): 'UTORRENT_PASSWORD': (str, 'uTorrent', ''), 'UTORRENT_USERNAME': (str, 'uTorrent', ''), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), + 'REALDEBRID_APIKEY': (str, 'Real-Debrid', ''), 'WAIT_UNTIL_RELEASE_DATE': (int, 'General', 0), 'WAFFLES': (int, 'Waffles', 0), 'WAFFLES_PASSKEY': (str, 'Waffles', ''), diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index ed3c136b6..68b5c0591 100755 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -27,7 +27,7 @@ from beets import logging as beetslogging from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError from beetsplug import lyrics as beetslyrics -from headphones import notifiers, utorrent, transmission, deluge, qbittorrent +from headphones import notifiers, utorrent, transmission, deluge, qbittorrent, realdebrid from headphones import db, albumart, librarysync from headphones import logger, helpers, mb, music_encoder from headphones import metadata @@ -61,6 +61,8 @@ def checkFolder(): torrent_folder_name, single = transmission.getFolder(album['TorrentHash']) elif headphones.CONFIG.TORRENT_DOWNLOADER == 4: torrent_folder_name, single = qbittorrent.getFolder(album['TorrentHash']) + elif headphones.CONFIG.TORRENT_DOWNLOADER == 5: + torrent_folder_name = realdebrid.getFolder(album['TorrentHash']) if torrent_folder_name: folder_name = torrent_folder_name @@ -207,6 +209,21 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal downloaded_track_list = [] downloaded_cuecount = 0 + if Kind == 'torrent' and headphones.CONFIG.TORRENT_DOWNLOADER == 5: + # Real-Debrid is the torrent downloader, so we check if the download is complete + # here, because it won't actually download the files locally until we tell it to + snatched = myDB.action('SELECT * from snatched WHERE AlbumID=? and FolderName=?', [albumid, os.path.basename(albumpath)]).fetchone() + try: + done = realdebrid.checkStatus(snatched['TorrentHash']) + except LookupError: + myDB.action('DELETE FROM snatched WHERE TorrentHash=?', [snatched['TorrentHash']]) + + if not done: + logger.info( + "Looks like " + os.path.basename(albumpath).decode(headphones.SYS_ENCODING, + 'replace') + " isn't complete yet. Will try again on the next run") + return + for r, d, f in os.walk(albumpath): for files in f: if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS): @@ -228,6 +245,10 @@ def verify(albumid, albumpath, Kind=None, forced=False, keep_original_folder=Fal if (headphones.CONFIG.KEEP_TORRENT_FILES and Kind == "torrent") or headphones.CONFIG.KEEP_ORIGINAL_FOLDER: keep_original_folder = True + # If we're using real-debrid, we don't want to keep the original + if headphones.CONFIG.TORRENT_DOWNLOADER == 5 and Kind == "torrent": + keep_original_folder = False + # Split cue before metadata check if headphones.CONFIG.CUE_SPLIT and downloaded_cuecount and downloaded_cuecount >= len( downloaded_track_list): @@ -495,6 +516,8 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, torrent_removed = deluge.removeTorrent(hash, True) elif headphones.CONFIG.TORRENT_DOWNLOADER == 2: torrent_removed = utorrent.removeTorrent(hash, True) + elif headphones.CONFIG.TORRENT_DOWNLOADER == 5: + torrent_removed = realdebrid.removeTorrent(hash) else: torrent_removed = qbittorrent.removeTorrent(hash, True) diff --git a/headphones/realdebrid.py b/headphones/realdebrid.py new file mode 100644 index 000000000..6ea5c1809 --- /dev/null +++ b/headphones/realdebrid.py @@ -0,0 +1,238 @@ +# This file is part of Headphones. +# +# Headphones 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. +# +# Headphones 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 Headphones. If not, see . + +import os +import urllib +import json +import urllib2 +import patoolib + +import headphones +from headphones import logger + + +class realdebridclient(object): + + def __init__(self, base_url=None, username=None, password=None, ): + + host = "https://api.real-debrid.com/rest/1.0" + + self.base_url = host + self.apikey = headphones.CONFIG.REALDEBRID_APIKEY + self.opener = self._make_opener() + + def _make_opener(self): + opener = urllib2.build_opener(urllib2.HTTPSHandler) + return opener + + def list(self): + params = '/torrents' + return self._action(params) + + def add_url(self, url): + # must be magnet + params = '/torrents/addMagnet' + data = { + 'magnet': url + } + + data = urllib.urlencode(data) + return self._action(params, data, 'application/x-www-form-urlencoded') + + def get_torrent(self, id): + params = '/torrents/info/' + id + return self._action(params) + + def select_files(self, id): + params = '/torrents/selectFiles/' + id + data = { + 'files': 'all' + } + + data = urllib.urlencode(data) + return self._action(params, data) + + def remove(self, id): + params = '/torrents/delete/' + id + return self._action(params, None, None, 'DELETE') + + def _unrestrict_link(self, url): + params = '/unrestrict/link' + data = { + 'link': url + } + + data = urllib.urlencode(data) + return self._action(params, data) + + def download_and_extract(self, url, id): + """Need to get torrent, determine # of files, then download accordingly""" + status, torrent = self.get_torrent(id) + + if status != 200: + raise ValueError('Http error occured when getting torrent: ' + status) + + try: + status, unrestricted_link = self._unrestrict_link(url) + + if status != 200: + raise ValueError('Http error occured when unrestricting real-debrid link: ' + status) + + download_url = unrestricted_link['download'] + + filename = torrent['original_filename'] + if unrestricted_link['mimeType'] == 'application/x-rar-compressed': + filename += '.rar' + + logger.info('Downloading file from real-debrid: ' + download_url) + urllib.urlretrieve(download_url, os.path.join(headphones.CONFIG.DOWNLOAD_TORRENT_DIR, filename)) + logger.info('Real-Debrid download complete!') + + if len(torrent['files']) == 1: + filename = torrent['filename'] + return filename + + # Got rar file from real-debrid, so we'll unrar it first + try: + archivename = os.path.join(headphones.CONFIG.DOWNLOAD_TORRENT_DIR, filename) + foldername = os.path.join(headphones.CONFIG.DOWNLOAD_TORRENT_DIR, torrent['original_filename']) + logger.info('Extracting rar: ' + archivename) + patoolib.extract_archive(archivename, outdir=foldername) + os.remove(archivename) + logger.info('Extract complete!') + return foldername + except Exception as err: + logger.error('Extracting Real-Debrid rar failed: ' + str(err)) + raise ValueError('Could not extract rar file, do you have patool set up properly?') + + except urllib2.HTTPError as err: + logger.debug('URL: ' + str(url)) + logger.debug('Real-Debrid raised the following error: ' + str(err)) + + def _action(self, action, body=None, content_type=None, method=None): + + url = self.base_url + action + '?auth_token=' + self.apikey + request = urllib2.Request(url) + + if body: + request.add_data(body) + request.add_header('Content-length', len(body)) + if content_type: + request.add_header('Content-type', content_type) + if method: + request.get_method = lambda: method + + try: + response = self.opener.open(request) + + if response.code == 201 or response.code == 204: + return response.code, None + + return response.code, json.loads(response.read()) + except urllib2.HTTPError as err: + logger.debug('URL: ' + str(url)) + logger.debug('Real-Debrid raised the following error: ' + str(err)) + + +def removeTorrent(hash): + RealDebridClient = realdebridclient() + status, torrents = RealDebridClient.list() + + for torrent in torrents: + if torrent['hash'].upper() == hash.upper(): + status, data = RealDebridClient.remove(torrent['id']) + if status == 204: + return True + + return False + + +def addTorrent(link): + RealDebridClient = realdebridclient() + status, data = RealDebridClient.add_url(link) + + if status != 201: + raise ValueError('Http error occured when adding magnet: ' + status) + + +def selectFiles(hash): + RealDebridClient = realdebridclient() + status, torrents = RealDebridClient.list() + + if status != 200: + raise ValueError('Http error occured when getting torrents: ' + status) + + for torrent in torrents: + if torrent['hash'].upper() == hash.upper(): + status, data = RealDebridClient.select_files(torrent['id']) + + if status != 204: + raise ValueError('Http error occured when selecting files: ' + status) + + +def getFolder(hash): + RealDebridClient = realdebridclient() + status, torrents = RealDebridClient.list() + + if status != 200: + raise ValueError('Http error occured when getting folder: ' + status) + + for torrent in torrents: + if torrent['hash'].upper() == hash.upper(): + + status, torrent_info = RealDebridClient.get_torrent(torrent['id']) + if status != 200: + return None + + if torrent_info['filename'] != "" or len(torrent_info['files']) == 1: + return headphones.CONFIG.DOWNLOAD_TORRENT_DIR + + directory = os.path.join(headphones.CONFIG.DOWNLOAD_TORRENT_DIR, torrent_info['original_filename']) + if not os.path.exists(directory): + os.makedirs(directory) + + return torrent_info['original_filename'] + + return None + + +def checkStatus(hash): + RealDebridClient = realdebridclient() + status, torrents = RealDebridClient.list() + + if status != 200: + raise ValueError('Http error occured when getting folder: ' + status) + + for torrent in torrents: + if torrent['hash'].upper() == hash.upper(): + if torrent['progress'] == 100: + RealDebridClient.download_and_extract(torrent['links'][0], torrent['id']) + return True + elif torrent['status'] == "waiting_files_selection": + RealDebridClient.select_files(torrent['id']) + return False + elif torrent['status'] == "error": + logger.error('Torrent status is error, removing...') + foldername = getFolder(hash) + directory = os.path.join(headphones.CONFIG.DOWNLOAD_TORRENT_DIR, foldername) + if os.path.exists(directory): + os.removedirs(directory) + RealDebridClient.remove(torrent['id']) + raise LookupError() + else: + return False + + logger.info('Hash ' + hash.upper() + ' not found on Real-Debrid') + raise LookupError() diff --git a/headphones/rutracker.py b/headphones/rutracker.py index 7185984dc..e0a4630ee 100644 --- a/headphones/rutracker.py +++ b/headphones/rutracker.py @@ -226,6 +226,18 @@ def utorrent_add_file(self, data): except Exception as e: logger.exception('Error adding file to utorrent %s', e) + # TODO get this working in utorrent.py + def realdebrid_add_file(self, data): + host = "https://api.real-debrid.com/rest/1.0" + apikey = headphones.CONFIG.REALDEBRID_APIKEY + + url = host + "/torrents/addTorrent?auth_token=" + apikey + + try: + self.session.post(url, data) + except Exception as e: + logger.exception('Error adding file to utorrent %s', e) + # TODO get this working in qbittorrent.py def qbittorrent_add_file(self, data): host = headphones.CONFIG.QBITTORRENT_HOST diff --git a/headphones/searcher.py b/headphones/searcher.py index 0dc81b546..a9c6b5cdf 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -36,7 +36,7 @@ import headphones from headphones.common import USER_AGENT from headphones import logger, db, helpers, classes, sab, nzbget, request -from headphones import utorrent, transmission, notifiers, rutracker, deluge, qbittorrent +from headphones import utorrent, transmission, notifiers, rutracker, deluge, qbittorrent, realdebrid from bencode import bencode, bdecode # Magnet to torrent services, for Black hole. Stolen from CouchPotato. @@ -1014,6 +1014,37 @@ def send_to_downloader(data, bestqual, album): seed_ratio = get_seed_ratio(bestqual[3]) if seed_ratio is not None: utorrent.setSeedRatio(torrentid, seed_ratio) + elif headphones.CONFIG.TORRENT_DOWNLOADER == 5: # real-debrid + logger.info("Sending torrent to Real-Debrid") + + # Add torrent + if bestqual[3] == 'rutracker.org': + ruobj.realdebrid_add_file(data) + else: + try: + realdebrid.addTorrent(bestqual[2]) + except ValueError as err: + logger.error('Torrent could not be added: ' + str(err)) + + # Get hash + torrentid = calculate_torrent_hash(bestqual[2], data) + if not torrentid: + logger.error('Torrent id could not be determined') + return + + if bestqual[3] != 'rutracker.org': + try: + realdebrid.selectFiles(torrentid) + except ValueError as err: + logger.error('Torrent files could not be selected: ' + str(err)) + + # Get folder + folder_name = realdebrid.getFolder(torrentid) + if folder_name: + logger.info('Torrent folder name: %s' % folder_name) + else: + logger.error('Torrent folder name could not be determined') + return else: # if headphones.CONFIG.TORRENT_DOWNLOADER == 4: logger.info("Sending torrent to QBiTorrent") diff --git a/headphones/torrentfinished.py b/headphones/torrentfinished.py index d90ffae49..d74a47d93 100644 --- a/headphones/torrentfinished.py +++ b/headphones/torrentfinished.py @@ -14,7 +14,7 @@ # along with Headphones. If not, see . -from headphones import db, utorrent, transmission, deluge, qbittorrent, logger +from headphones import db, utorrent, transmission, deluge, qbittorrent, realdebrid, logger import headphones @@ -39,6 +39,8 @@ def checkTorrentFinished(): torrent_removed = utorrent.removeTorrent(hash, True) elif headphones.CONFIG.TORRENT_DOWNLOADER == 3: torrent_removed = deluge.removeTorrent(hash, True) + elif headphones.CONFIG.TORRENT_DOWNLOADER == 5: + torrent_removed = realdebrid.removeTorrent(hash) else: torrent_removed = qbittorrent.removeTorrent(hash, True) diff --git a/headphones/webserve.py b/headphones/webserve.py index 6d95712a4..8cefd21d9 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -1181,6 +1181,7 @@ def config(self): "deluge_label": headphones.CONFIG.DELUGE_LABEL, "deluge_done_directory": headphones.CONFIG.DELUGE_DONE_DIRECTORY, "deluge_paused": checked(headphones.CONFIG.DELUGE_PAUSED), + "realdebrid_apikey": headphones.CONFIG.REALDEBRID_APIKEY, "utorrent_host": headphones.CONFIG.UTORRENT_HOST, "utorrent_username": headphones.CONFIG.UTORRENT_USERNAME, "utorrent_password": headphones.CONFIG.UTORRENT_PASSWORD, @@ -1193,6 +1194,7 @@ def config(self): "torrent_downloader_utorrent": radio(headphones.CONFIG.TORRENT_DOWNLOADER, 2), "torrent_downloader_deluge": radio(headphones.CONFIG.TORRENT_DOWNLOADER, 3), "torrent_downloader_qbittorrent": radio(headphones.CONFIG.TORRENT_DOWNLOADER, 4), + "torrent_downloader_realdebrid": radio(headphones.CONFIG.TORRENT_DOWNLOADER, 5), "download_dir": headphones.CONFIG.DOWNLOAD_DIR, "use_blackhole": checked(headphones.CONFIG.BLACKHOLE), "blackhole_dir": headphones.CONFIG.BLACKHOLE_DIR, diff --git a/lib/patoolib/__init__.py b/lib/patoolib/__init__.py new file mode 100644 index 000000000..f50d887e3 --- /dev/null +++ b/lib/patoolib/__init__.py @@ -0,0 +1,767 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2016 Bastian Kleineidam +# +# 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 . +from __future__ import print_function +import sys +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.") +import os +import shutil +import stat +import importlib +# PEP 396 +from .configuration import App, Version as __version__ +__all__ = ['list_formats', 'list_archive', 'extract_archive', 'test_archive', + 'create_archive', 'diff_archives', 'search_archive', 'repack_archive', + 'recompress_archive'] + + +# Supported archive commands +ArchiveCommands = ('list', 'extract', 'test', 'create') + +# Supported archive formats +ArchiveFormats = ( + '7z', 'ace', 'adf', 'alzip', 'ape', 'ar', 'arc', 'arj', + 'bzip2', 'cab', 'chm', 'compress', 'cpio', 'deb', 'dms', + 'flac', 'gzip', 'iso', 'lrzip', 'lzh', 'lzip', 'lzma', 'lzop', + 'rar', 'rpm', 'rzip', 'shar', 'shn', 'tar', 'vhd', 'xz', + 'zip', 'zoo', 'zpaq') + +# Supported compressions (used with tar for example) +# Note that all compressions must also be archive formats +ArchiveCompressions = ('bzip2', 'compress', 'gzip', 'lzip', 'lzma', 'xz') + +# Map MIME types to archive format +ArchiveMimetypes = { + 'application/gzip': 'gzip', + 'application/java-archive': 'zip', + 'application/rar': 'rar', + 'application/vnd.ms-cab-compressed': 'cab', + 'application/x-7z-compressed': '7z', + 'application/x-ace': 'ace', + 'application/x-adf': 'adf', + 'application/x-alzip': 'alzip', + 'application/x-archive': 'ar', + 'application/x-arc': 'arc', + 'application/x-arj': 'arj', + 'application/x-bzip2': 'bzip2', + 'application/x-cab': 'cab', + 'application/x-chm': 'chm', + 'application/x-compress': 'compress', + 'application/x-cpio': 'cpio', + 'application/x-debian-package': 'deb', + 'application/x-dms': 'dms', + 'application/x-gzip': 'gzip', + 'application/x-iso9660-image': 'iso', + 'application/x-lzop': 'lzop', + 'application/x-lzma': 'lzma', + 'application/x-lzip': 'lzip', + 'application/x-lha': 'lzh', + 'application/x-lrzip': 'lrzip', + 'application/x-lzh': 'lzh', + 'application/x-rar': 'rar', + 'application/x-redhat-package-manager': 'rpm', + 'application/x-rpm': 'rpm', + 'application/x-rzip': 'rzip', + 'application/x-shar': 'shar', + 'application/x-tar': 'tar', + 'application/x-vhd': 'vhd', + 'application/x-xz': 'xz', + 'application/x-zip-compressed': 'zip', + 'application/x-zoo': 'zoo', + 'application/zip': 'zip', + 'application/zpaq': 'zpaq', + 'audio/x-ape': 'ape', + 'audio/x-shn': 'shn', + 'audio/flac': 'flac', +} + +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. +ArchivePrograms = { + 'ace': { + 'extract': ('unace',), + 'test': ('unace',), + 'list': ('unace',), + }, + 'adf': { + 'extract': ('unadf',), + 'test': ('unadf',), + 'list': ('unadf',), + }, + 'alzip': { + 'extract': ('unalz',), + 'test': ('unalz',), + 'list': ('unalz',), + }, + 'ape': { + 'create': ('mac',), + 'extract': ('mac',), + 'list': ('py_echo',), + 'test': ('mac',), + }, + 'ar': { + None: ('ar',), + }, + 'arc': { + None: ('arc',), + 'extract': ('nomarch',), + 'test': ('nomarch',), + 'list': ('nomarch',), + }, + 'bzip2': { + None: ('7z', '7za'), + 'extract': ('pbzip2', 'lbzip2', 'bzip2', 'py_bz2'), + 'test': ('pbzip2', 'lbzip2', 'bzip2'), + 'create': ('pbzip2', 'lbzip2', 'bzip2', 'py_bz2'), + 'list': ('py_echo'), + }, + 'cab': { + 'extract': ('cabextract', '7z'), + 'create': ('lcab',), + 'list': ('cabextract', '7z'), + 'test': ('cabextract', '7z'), + }, + 'chm': { + 'extract': ('archmage', 'extract_chmLib'), + 'test': ('archmage',), + }, + 'flac': { + 'extract': ('flac',), + 'test': ('flac',), + 'create': ('flac',), + 'list': ('py_echo',), + }, + 'tar': { + None: ('tar', 'star', 'bsdtar', 'py_tarfile'), + }, + 'zip': { + None: ('7z', '7za', 'py_zipfile'), + 'extract': ('unzip',), + 'list': ('unzip',), + 'test': ('zip', 'unzip',), + 'create': ('zip',), + }, + 'gzip': { + None: ('7z', '7za', 'pigz', 'gzip'), + 'extract': ('py_gzip',), + 'create': ('zopfli', 'py_gzip'), + }, + 'iso': { + 'extract': ('7z',), + 'list': ('7z', 'isoinfo'), + 'test': ('7z',), + 'create': ('genisoimage',), + }, + 'lzh': { + None: ('lha',), + 'extract': ('lhasa',), + }, + 'lzip': { + 'extract': ('plzip', 'lzip', 'clzip', 'pdlzip'), + 'list': ('py_echo',), + 'test': ('plzip', 'lzip', 'clzip', 'pdlzip'), + 'create': ('plzip', 'lzip', 'clzip', 'pdlzip'), + }, + 'lrzip': { + 'extract': ('lrzip',), + 'list': ('py_echo',), + 'test': ('lrzip',), + 'create': ('lrzip',), + }, + 'compress': { + 'extract': ('gzip', '7z', '7za', 'uncompress.real'), + 'list': ('7z', '7za', 'py_echo',), + 'test': ('gzip', '7z', '7za'), + 'create': ('compress',), + }, + '7z': { + None: ('7z', '7za', '7zr'), + }, + 'rar': { + None: ('rar',), + 'extract': ('unrar', '7z'), + 'list': ('unrar', '7z'), + 'test': ('unrar', '7z'), + }, + 'arj': { + None: ('arj',), + 'extract': ('7z',), + 'list': ('7z',), + 'test': ('7z',), + }, + 'cpio': { + 'extract': ('cpio', 'bsdcpio', '7z'), + 'list': ('cpio', 'bsdcpio', '7z'), + 'test': ('cpio', 'bsdcpio', '7z',), + 'create': ('cpio', 'bsdcpio'), + }, + 'rpm': { + 'extract': ('rpm2cpio', '7z'), + 'list': ('rpm', '7z', '7za'), + 'test': ('rpm', '7z'), + }, + 'deb': { + 'extract': ('dpkg-deb', '7z'), + 'list': ('dpkg-deb', '7z'), + 'test': ('dpkg-deb', '7z'), + }, + 'dms': { + 'extract': ('xdms',), + 'list': ('xdms',), + 'test': ('xdms',), + }, + 'lzop': { + None: ('lzop',), + }, + 'lzma': { + 'extract': ('7z', 'lzma', 'xz') + py_lzma, + 'list': ('7z', 'py_echo'), + 'test': ('7z', 'lzma', 'xz'), + 'create': ('lzma', 'xz') + py_lzma, + }, + 'rzip': { + 'extract': ('rzip',), + 'list': ('py_echo',), + 'create': ('rzip',), + }, + 'shar': { + 'create': ('shar',), + 'extract': ('unshar',), + }, + 'shn': { + 'extract': ('shorten',), + 'list': ('py_echo',), + 'create': ('shorten',), + }, + 'vhd': { + 'extract': ('7z',), + 'list': ('7z',), + 'test': ('7z',), + }, + 'xz': { + None: ('xz', '7z'), + 'extract': py_lzma, + 'create': py_lzma, + }, + 'zoo': { + None: ('zoo',), + }, + 'zpaq': { + None: ('zpaq',), + }, +} + +# List those programs that have different python module names because of +# Python module naming restrictions. +ProgramModules = { + '7z': 'p7zip', + '7za': 'p7azip', + '7zr': 'p7rzip', + 'uncompress.real': 'uncompress', + 'dpkg-deb': 'dpkg', + 'extract_chmlib': 'chmlib', +} + + +from . import util + +def get_archive_format (filename): + """Detect filename archive format and optional compression.""" + mime, compression = util.guess_mime(filename) + if not (mime or compression): + 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 + + +def check_archive_format (format, compression): + """Make sure format and compression is known.""" + 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) + + +def find_archive_program (format, command, program=None): + """Find suitable archive program for given format and mode.""" + commands = ArchivePrograms[format] + programs = [] + if program is not None: + # try a specific program first + programs.append(program) + # first try the universal programs with key None + for key in (None, command): + if key in commands: + programs.extend(commands[key]) + if not programs: + raise util.PatoolError("%s archive format `%s' is not supported" % (command, format)) + # 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) + if exe: + if program == '7z' and format == 'rar' and not util.p7zip_supports_rar(): + continue + 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))) + + +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. + """ + if program in ('tar', 'star', 'bsdtar', 'py_tarfile'): + return compression in ('gzip', 'bzip2') + py_lzma + return False + + +def list_formats (): + """Print information about available archive formats to stdout.""" + print("Archive programs of", App) + print("Archive programs are searched in the following directories:") + print(util.system_search_path()) + print() + for format in ArchiveFormats: + print(format, "files:") + for command in ArchiveCommands: + programs = ArchivePrograms[format] + if command not in programs and None not in programs: + print(" %8s: - (not supported)" % command) + continue + try: + program = find_archive_program(format, command) + print(" %8s: %s" % (command, program), end=' ') + if format == 'tar': + encs = [x for x in ArchiveCompressions if util.find_program(x)] + if encs: + print("(supported compressions: %s)" % ", ".join(encs), end=' ') + elif format == '7z': + if util.p7zip_supports_rar(): + print("(rar archives supported)", end=' ') + else: + print("(rar archives not supported)", end=' ') + print() + except util.PatoolError: + # display information what programs can handle this archive format + handlers = programs.get(None, programs.get(command)) + print(" %8s: - (no program found; install %s)" % + (command, util.strlist_with_or(handlers))) + + +def check_program_compression(archive, command, program, compression): + """Check if a program supports the given compression.""" + program = os.path.basename(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)) + + +def move_outdir_orphan (outdir): + """Move a single file or directory inside outdir a level up. + Never overwrite files. + 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) or os.path.islink(dst): + return (False, "local file exists") + shutil.move(src, dst) + os.rmdir(outdir) + return (True, entries[0]) + return (False, "multiple files in root") + + +def run_archive_cmdlist (archive_cmdlist, verbosity=0): + """Run archive command.""" + # archive_cmdlist is a command list with optional keyword arguments + if isinstance(archive_cmdlist, tuple): + cmdlist, runkwargs = archive_cmdlist + else: + cmdlist, runkwargs = archive_cmdlist, {} + return util.run_checked(cmdlist, verbosity=verbosity, **runkwargs) + + +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.""" + 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) + + +def _extract_archive(archive, verbosity=0, interactive=True, outdir=None, + program=None, format=None, compression=None): + """Extract an archive. + @return: output directory if command is 'extract', else None + """ + if format is None: + format, compression = get_archive_format(archive) + check_archive_format(format, compression) + program = find_archive_program(format, 'extract', program=program) + check_program_compression(archive, 'extract', program, compression) + get_archive_cmdlist = get_archive_cmdlist_func(program, 'extract', format) + if outdir is None: + outdir = util.tmpdir(dir=".") + do_cleanup_outdir = True + else: + do_cleanup_outdir = False + try: + cmdlist = get_archive_cmdlist(archive, compression, program, verbosity, interactive, outdir) + if cmdlist: + # an empty command list means the get_archive_cmdlist() function + # already handled the command (eg. when it's a builtin Python + # function) + run_archive_cmdlist(cmdlist, verbosity=verbosity) + if do_cleanup_outdir: + target, msg = cleanup_outdir(outdir, archive) + else: + target, msg = outdir, "`%s'" % outdir + if verbosity >= 0: + util.log_info("... %s extracted to %s." % (archive, msg)) + return target + finally: + # try to remove an empty temporary output directory + if do_cleanup_outdir: + try: + os.rmdir(outdir) + except OSError: + pass + + +def _create_archive(archive, filenames, verbosity=0, interactive=True, + program=None, format=None, compression=None): + """Create an archive.""" + if format is None: + format, compression = get_archive_format(archive) + check_archive_format(format, compression) + program = find_archive_program(format, 'create', program=program) + check_program_compression(archive, 'create', program, compression) + get_archive_cmdlist = get_archive_cmdlist_func(program, 'create', format) + origarchive = None + if 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") + cmdlist = get_archive_cmdlist(archive, compression, program, verbosity, interactive, filenames) + if cmdlist: + # an empty command list means the get_archive_cmdlist() function + # already handled the command (eg. when it's a builtin Python + # function) + run_archive_cmdlist(cmdlist, verbosity=verbosity) + if origarchive: + shutil.move(archive, origarchive) + + +def _handle_archive(archive, command, verbosity=0, interactive=True, + program=None, format=None, compression=None): + """Test and list archives.""" + if format is None: + format, compression = get_archive_format(archive) + check_archive_format(format, compression) + if command not in ('list', 'test'): + raise util.PatoolError("invalid archive command `%s'" % command) + program = find_archive_program(format, command, program=program) + check_program_compression(archive, command, program, compression) + get_archive_cmdlist = get_archive_cmdlist_func(program, command, format) + # prepare keyword arguments for command list + cmdlist = get_archive_cmdlist(archive, compression, program, verbosity, interactive) + if cmdlist: + # an empty command list means the get_archive_cmdlist() function + # already handled the command (eg. when it's a builtin Python + # function) + run_archive_cmdlist(cmdlist, verbosity=verbosity) + + +def get_archive_cmdlist_func (program, command, format): + """Get the Python function that executes the given program.""" + # get python module for given archive program + key = util.stripext(os.path.basename(program).lower()) + modulename = ".programs." + ProgramModules.get(key, key) + # import the module + try: + 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) + + +def rmtree_log_error (func, path, exc): + """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, verbosity=0, interactive=True): + """Show differences between two archives. + @return 0 if archives are the same, else 1 + @raises: PatoolError on errors + """ + if util.is_same_file(archive1, archive2): + return 0 + diff = util.find_program("diff") + if not diff: + msg = "The diff(1) program is required for showing archive differences, please install it." + raise util.PatoolError(msg) + tmpdir1 = util.tmpdir() + try: + path1 = _extract_archive(archive1, outdir=tmpdir1, verbosity=-1) + tmpdir2 = util.tmpdir() + try: + path2 = _extract_archive(archive2, outdir=tmpdir2, verbosity=-1) + return util.run_checked([diff, "-urN", path1, path2], verbosity=1, ret_ok=(0, 1)) + finally: + shutil.rmtree(tmpdir2, onerror=rmtree_log_error) + finally: + shutil.rmtree(tmpdir1, onerror=rmtree_log_error) + + +def _search_archive(pattern, archive, verbosity=0, interactive=True): + """Search for given pattern in an archive.""" + 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) + tmpdir = util.tmpdir() + try: + path = _extract_archive(archive, outdir=tmpdir, verbosity=-1) + return util.run_checked([grep, "-r", "-e", pattern, "."], ret_ok=(0, 1), verbosity=1, cwd=path) + finally: + shutil.rmtree(tmpdir, onerror=rmtree_log_error) + + +def _repack_archive (archive1, archive2, verbosity=0, interactive=True): + """Repackage an archive to a different format.""" + 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 + util.link_or_copy(archive1, archive2, verbosity=verbosity) + return + tmpdir = util.tmpdir() + try: + kwargs = dict(verbosity=verbosity, outdir=tmpdir) + same_format = (format1 == format2 and compression1 and compression2) + if same_format: + # only decompress since the format is the same + kwargs['format'] = compression1 + path = _extract_archive(archive1, **kwargs) + archive = os.path.abspath(archive2) + files = tuple(os.listdir(path)) + olddir = os.getcwd() + os.chdir(path) + try: + kwargs = dict(verbosity=verbosity, interactive=interactive) + if same_format: + # only compress since the format is the same + kwargs['format'] = compression2 + _create_archive(archive, files, **kwargs) + finally: + os.chdir(olddir) + finally: + shutil.rmtree(tmpdir, onerror=rmtree_log_error) + + +def _recompress_archive(archive, verbosity=0, interactive=True): + """Try to recompress an archive to smaller size.""" + format, compression = get_archive_format(archive) + if compression: + # only recompress the compression itself (eg. for .tar.xz) + format = compression + tmpdir = util.tmpdir() + tmpdir2 = util.tmpdir() + base, ext = os.path.splitext(os.path.basename(archive)) + archive2 = util.get_single_outfile(tmpdir2, base, extension=ext) + try: + # extract + kwargs = dict(verbosity=verbosity, format=format, outdir=tmpdir) + path = _extract_archive(archive, **kwargs) + # compress to new file + olddir = os.getcwd() + os.chdir(path) + try: + kwargs = dict(verbosity=verbosity, interactive=interactive, format=format) + files = tuple(os.listdir(path)) + _create_archive(archive2, files, **kwargs) + finally: + os.chdir(olddir) + # check file sizes and replace if new file is smaller + filesize = util.get_filesize(archive) + filesize2 = util.get_filesize(archive2) + if filesize2 < filesize: + # replace file + os.remove(archive) + shutil.move(archive2, archive) + diffsize = filesize - filesize2 + return "... recompressed file is now %s smaller." % util.strsize(diffsize) + finally: + shutil.rmtree(tmpdir, onerror=rmtree_log_error) + shutil.rmtree(tmpdir2, onerror=rmtree_log_error) + return "... recompressed file is not smaller, leaving archive as is." + + +# the patool library API + +def extract_archive(archive, verbosity=0, outdir=None, program=None, interactive=True): + """Extract given archive.""" + util.check_existing_filename(archive) + if verbosity >= 0: + util.log_info("Extracting %s ..." % archive) + return _extract_archive(archive, verbosity=verbosity, interactive=interactive, outdir=outdir, program=program) + + +def list_archive(archive, verbosity=1, program=None, interactive=True): + """List given archive.""" + # Set default verbosity to 1 since the listing output should be visible. + util.check_existing_filename(archive) + if verbosity >= 0: + util.log_info("Listing %s ..." % archive) + return _handle_archive(archive, 'list', verbosity=verbosity, + interactive=interactive, program=program) + + +def test_archive(archive, verbosity=0, program=None, interactive=True): + """Test given archive.""" + util.check_existing_filename(archive) + if verbosity >= 0: + util.log_info("Testing %s ..." % archive) + res = _handle_archive(archive, 'test', verbosity=verbosity, + interactive=interactive, program=program) + if verbosity >= 0: + util.log_info("... tested ok.") + return res + + +def create_archive(archive, filenames, verbosity=0, program=None, interactive=True): + """Create given archive with given files.""" + util.check_new_filename(archive) + util.check_archive_filelist(filenames) + if verbosity >= 0: + util.log_info("Creating %s ..." % archive) + res = _create_archive(archive, filenames, verbosity=verbosity, + interactive=interactive, program=program) + if verbosity >= 0: + util.log_info("... %s created." % archive) + return res + + +def diff_archives(archive1, archive2, verbosity=0, interactive=True): + """Print differences between two archives.""" + util.check_existing_filename(archive1) + util.check_existing_filename(archive2) + if verbosity >= 0: + util.log_info("Comparing %s with %s ..." % (archive1, archive2)) + res = _diff_archives(archive1, archive2, verbosity=verbosity, interactive=interactive) + if res == 0 and verbosity >= 0: + util.log_info("... no differences found.") + + +def search_archive(pattern, archive, verbosity=0, interactive=True): + """Search pattern in archive members.""" + if not pattern: + raise util.PatoolError("empty search pattern") + util.check_existing_filename(archive) + if verbosity >= 0: + util.log_info("Searching %r in %s ..." % (pattern, archive)) + res = _search_archive(pattern, archive, verbosity=verbosity, interactive=interactive) + if res == 1 and verbosity >= 0: + util.log_info("... %r not found" % pattern) + return res + + +def repack_archive (archive, archive_new, verbosity=0, interactive=True): + """Repack archive to different file and/or format.""" + util.check_existing_filename(archive) + util.check_new_filename(archive_new) + if verbosity >= 0: + util.log_info("Repacking %s to %s ..." % (archive, archive_new)) + res = _repack_archive(archive, archive_new, verbosity=verbosity, interactive=interactive) + if verbosity >= 0: + util.log_info("... repacking successful.") + return res + + +def recompress_archive(archive, verbosity=0, interactive=True): + """Recompress an archive to hopefully smaller size.""" + util.check_existing_filename(archive) + util.check_writable_filename(archive) + if verbosity >= 0: + util.log_info("Recompressing %s ..." % (archive,)) + res = _recompress_archive(archive, verbosity=verbosity, interactive=interactive) + if res and verbosity >= 0: + util.log_info(res) + return 0 diff --git a/lib/patoolib/configuration.py b/lib/patoolib/configuration.py new file mode 100644 index 000000000..05ecf17e2 --- /dev/null +++ b/lib/patoolib/configuration.py @@ -0,0 +1,16 @@ +# Copyright (C) 2013-2015 Bastian Kleineidam +""" +Define basic configuration data like version or application name. +""" +import _patool_configdata as configdata + +Version = configdata.version +ReleaseDate = configdata.release_date +AppName = configdata.name +App = AppName+u" "+Version +Author = configdata.author +Maintainer = configdata.maintainer +Copyright = u"Copyright (C) 2004-2015 " + Author +Url = configdata.url +SupportUrl = u"https://github.com/wummel/patool/issues/" +Email = configdata.author_email diff --git a/lib/patoolib/programs/__init__.py b/lib/patoolib/programs/__init__.py new file mode 100644 index 000000000..275aa84b9 --- /dev/null +++ b/lib/patoolib/programs/__init__.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +from .. import util + +def extract_singlefile_standard (archive, compression, cmd, verbosity, interactive, outdir): + """Standard routine to extract a singlefile archive (like gzip).""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + outfile = util.get_single_outfile(outdir, archive) + cmdlist.extend(['-c', '-d', '--', util.shell_quote(archive), '>', + util.shell_quote(outfile)]) + return (cmdlist, {'shell': True}) + + +def test_singlefile_standard (archive, compression, cmd, verbosity, interactive): + """Standard routine to test a singlefile archive (like gzip).""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-t', '--', archive]) + return cmdlist + + +def create_singlefile_standard (archive, compression, cmd, verbosity, interactive, filenames): + """Standard routine to create a singlefile archive (like gzip).""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-c', '--']) + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/ar.py b/lib/patoolib/programs/ar.py new file mode 100644 index 000000000..115f27c81 --- /dev/null +++ b/lib/patoolib/programs/ar.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the ar program.""" +import os + +def extract_ar (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a AR archive.""" + opts = 'x' + if verbosity > 1: + opts += 'v' + cmdlist = [cmd, opts, os.path.abspath(archive)] + return (cmdlist, {'cwd': outdir}) + +def list_ar (archive, compression, cmd, verbosity, interactive): + """List a AR archive.""" + opts = 't' + if verbosity > 1: + opts += 'v' + return [cmd, opts, archive] + +test_ar = list_ar + +def create_ar (archive, compression, cmd, verbosity, interactive, filenames): + """Create a AR archive.""" + opts = 'rc' + if verbosity > 1: + opts += 'v' + cmdlist = [cmd, opts, archive] + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/arc.py b/lib/patoolib/programs/arc.py new file mode 100644 index 000000000..cce9679e7 --- /dev/null +++ b/lib/patoolib/programs/arc.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the arc program.""" +import os + +def extract_arc (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a ARC archive.""" + # Since extracted files will be placed in the current directory, + # the cwd argument has to be the output directory. + cmdlist = [cmd, 'x', os.path.abspath(archive)] + return (cmdlist, {'cwd': outdir}) + +def list_arc (archive, compression, cmd, verbosity, interactive): + """List a ARC archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('v') + else: + cmdlist.append('l') + cmdlist.append(archive) + return cmdlist + +def test_arc (archive, compression, cmd, verbosity, interactive): + """Test a ARC archive.""" + return [cmd, 't', archive] + +def create_arc (archive, compression, cmd, verbosity, interactive, filenames): + """Create a ARC archive.""" + cmdlist = [cmd, 'a', archive] + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/archmage.py b/lib/patoolib/programs/archmage.py new file mode 100644 index 000000000..b9c42ca10 --- /dev/null +++ b/lib/patoolib/programs/archmage.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the archmage program.""" +import os +from .. import util + + +def extract_chm (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a CHM archive.""" + # archmage can only extract in non-existing directories + # so a nice dirname is created + name = util.get_single_outfile("", archive) + outfile = os.path.join(outdir, name) + return [cmd, '-x', os.path.abspath(archive), outfile] + + +def test_chm (archive, compression, cmd, verbosity, interactive): + """Test a CHM archive.""" + return [cmd, '-d', os.path.abspath(archive)] diff --git a/lib/patoolib/programs/arj.py b/lib/patoolib/programs/arj.py new file mode 100644 index 000000000..2547c0e18 --- /dev/null +++ b/lib/patoolib/programs/arj.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the arj program.""" + +def extract_arj (archive, compression, cmd, verbosity, interactive, outdir): + """Extract an ARJ archive.""" + cmdlist = [cmd, 'x', '-r'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend([archive, outdir]) + return cmdlist + + +def list_arj (archive, compression, cmd, verbosity, interactive): + """List an ARJ archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('v') + else: + cmdlist.append('l') + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-r', archive]) + return cmdlist + + +def test_arj (archive, compression, cmd, verbosity, interactive): + """Test an ARJ archive.""" + cmdlist = [cmd, 't', '-r'] + if not interactive: + cmdlist.append('-y') + cmdlist.append(archive) + return cmdlist + + +def create_arj (archive, compression, cmd, verbosity, interactive, filenames): + """Create an ARJ archive.""" + cmdlist = [cmd, 'a', '-r'] + if not interactive: + cmdlist.append('-y') + cmdlist.append(archive) + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/bsdcpio.py b/lib/patoolib/programs/bsdcpio.py new file mode 100644 index 000000000..bbca4594d --- /dev/null +++ b/lib/patoolib/programs/bsdcpio.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the bsdcpio program.""" +from .cpio import extract_cpio, list_cpio, test_cpio, create_cpio + diff --git a/lib/patoolib/programs/bsdtar.py b/lib/patoolib/programs/bsdtar.py new file mode 100644 index 000000000..9981ca4cc --- /dev/null +++ b/lib/patoolib/programs/bsdtar.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the BSD tar program.""" + +from .tar import extract_tar, list_tar, test_tar, create_tar diff --git a/lib/patoolib/programs/bzip2.py b/lib/patoolib/programs/bzip2.py new file mode 100644 index 000000000..7185e28bf --- /dev/null +++ b/lib/patoolib/programs/bzip2.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the bzip2 program.""" +from .. import util +from . import extract_singlefile_standard, test_singlefile_standard + +extract_bzip2 = extract_singlefile_standard +test_bzip2 = test_singlefile_standard + +def create_bzip2 (archive, compression, cmd, verbosity, interactive, filenames): + """Create a BZIP2 archive.""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-c', '-z', '-9', '--']) + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/cabextract.py b/lib/patoolib/programs/cabextract.py new file mode 100644 index 000000000..0eb205bbc --- /dev/null +++ b/lib/patoolib/programs/cabextract.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the cabextract program.""" + +def extract_cab (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a CAB archive.""" + cmdlist = [cmd, '-d', outdir] + if verbosity > 0: + cmdlist.append('-v') + cmdlist.append(archive) + return cmdlist + +def list_cab (archive, compression, cmd, verbosity, interactive): + """List a CAB archive.""" + cmdlist = [cmd, '-l'] + if verbosity > 0: + cmdlist.append('-v') + cmdlist.append(archive) + return cmdlist + +def test_cab (archive, compression, cmd, verbosity, interactive): + """Test a CAB archive.""" + return [cmd, '-t', archive] diff --git a/lib/patoolib/programs/chmlib.py b/lib/patoolib/programs/chmlib.py new file mode 100644 index 000000000..7c92e5440 --- /dev/null +++ b/lib/patoolib/programs/chmlib.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the chmlib programs.""" +import os + + +def extract_chm (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a CHM archive.""" + return [cmd, os.path.abspath(archive), outdir] diff --git a/lib/patoolib/programs/clzip.py b/lib/patoolib/programs/clzip.py new file mode 100644 index 000000000..2e03ea197 --- /dev/null +++ b/lib/patoolib/programs/clzip.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2011-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the clzip program.""" +from . import extract_singlefile_standard, test_singlefile_standard +from .. import util + + +extract_lzip = extract_singlefile_standard +test_lzip = test_singlefile_standard + +def create_lzip(archive, compression, cmd, verbosity, interactive, filenames): + """Create an LZIP archive.""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-c', '-9', '--']) + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/compress.py b/lib/patoolib/programs/compress.py new file mode 100644 index 000000000..0a1074832 --- /dev/null +++ b/lib/patoolib/programs/compress.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the uncompress.real program.""" +from .. import util + + +def create_compress (archive, compression, cmd, verbosity, interactive, filenames): + """Create a compressed archive.""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.append('-c') + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/cpio.py b/lib/patoolib/programs/cpio.py new file mode 100644 index 000000000..74c9957d6 --- /dev/null +++ b/lib/patoolib/programs/cpio.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the cpio program.""" +import os +import sys +from .. import util + +def extract_cpio (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a CPIO archive.""" + cmdlist = [util.shell_quote(cmd), '--extract', '--make-directories', + '--preserve-modification-time'] + if sys.platform.startswith('linux') and not cmd.endswith('bsdcpio'): + cmdlist.extend(['--no-absolute-filenames', + '--force-local', '--nonmatching', r'"*\.\.*"']) + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['<', util.shell_quote(os.path.abspath(archive))]) + return (cmdlist, {'cwd': outdir, 'shell': True}) + + +def list_cpio (archive, compression, cmd, verbosity, interactive): + """List a CPIO archive.""" + cmdlist = [cmd, '-i', '-t'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-F', archive]) + return cmdlist + +test_cpio = list_cpio + +def create_cpio(archive, compression, cmd, verbosity, interactive, filenames): + """Create a CPIO archive.""" + cmdlist = [util.shell_quote(cmd), '--create'] + if verbosity > 1: + cmdlist.append('-v') + if len(filenames) != 0: + findcmd = ['find'] + findcmd.extend([util.shell_quote(x) for x in filenames]) + findcmd.extend(['-print0', '|']) + cmdlist[0:0] = findcmd + cmdlist.append('-0') + cmdlist.extend([">", util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/dpkg.py b/lib/patoolib/programs/dpkg.py new file mode 100644 index 000000000..3b8ae0d81 --- /dev/null +++ b/lib/patoolib/programs/dpkg.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the dpkg-deb program.""" + +def extract_deb (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a DEB archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('--vextract') + else: + cmdlist.append('--extract') + cmdlist.extend(['--', archive, outdir]) + return cmdlist + +def list_deb (archive, compression, cmd, verbosity, interactive): + """List a DEB archive.""" + return [cmd, '--contents', '--', archive] + +test_deb = list_deb diff --git a/lib/patoolib/programs/flac.py b/lib/patoolib/programs/flac.py new file mode 100644 index 000000000..93b94ef2d --- /dev/null +++ b/lib/patoolib/programs/flac.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the flac program.""" +from .. import util + +def extract_flac (archive, compression, cmd, verbosity, interactive, outdir): + """Decompress a FLAC archive to a WAV file.""" + outfile = util.get_single_outfile(outdir, archive, extension=".wav") + cmdlist = [cmd, '--decode', archive, '--output-name', outfile] + return cmdlist + + +def create_flac (archive, compression, cmd, verbosity, interactive, filenames): + """Compress a WAV file to a FLAC archive.""" + cmdlist = [cmd, filenames[0], '--best', '--output-name', archive] + return cmdlist + + +def test_flac (archive, compression, cmd, verbosity, interactive): + """Test a FLAC file.""" + return [cmd, '--test', archive] diff --git a/lib/patoolib/programs/genisoimage.py b/lib/patoolib/programs/genisoimage.py new file mode 100644 index 000000000..5cd4153ab --- /dev/null +++ b/lib/patoolib/programs/genisoimage.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2013-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the genisoimage program.""" + +def create_iso (archive, compression, cmd, verbosity, interactive, filenames): + """Create an ISO archive.""" + # Use Joliet (-J) and Rock-Ridge (-r) format. + cmdlist = [cmd, '-r', '-J'] + if verbosity > 1: + cmdlist.append('-v') + if verbosity > 2: + cmdlist.append('-v') + cmdlist.extend(['-o', archive]) + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/gzip.py b/lib/patoolib/programs/gzip.py new file mode 100644 index 000000000..8490ff436 --- /dev/null +++ b/lib/patoolib/programs/gzip.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the gzip program.""" +from . import extract_singlefile_standard, test_singlefile_standard +from .. import util + +extract_gzip = extract_compress = extract_singlefile_standard +test_gzip = test_compress = test_singlefile_standard + + +def create_gzip(archive, compression, cmd, verbosity, interactive, filenames): + """Create a GZIP archive.""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-c', '-9', '--']) + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) + + +def list_gzip (archive, compression, cmd, verbosity, interactive): + """List a GZIP archive.""" + cmdlist = [cmd] + if verbosity > 0: + cmdlist.append('-v') + cmdlist.extend(['-l', '--', archive]) + return cmdlist diff --git a/lib/patoolib/programs/isoinfo.py b/lib/patoolib/programs/isoinfo.py new file mode 100644 index 000000000..bd648b689 --- /dev/null +++ b/lib/patoolib/programs/isoinfo.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2013-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the isoinfo program.""" + +def list_iso (archive, compression, cmd, verbosity, interactive): + """List an ISO archive.""" + # Use Joliet (-J) and Rock-Ridge (-R) options. + return [cmd, '-l', '-R', '-J', '-i', archive] diff --git a/lib/patoolib/programs/lbzip2.py b/lib/patoolib/programs/lbzip2.py new file mode 100644 index 000000000..0a8f5e845 --- /dev/null +++ b/lib/patoolib/programs/lbzip2.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the pbzip2 program.""" +# bzip2 and lbzip2 are compatible +from .bzip2 import extract_bzip2, test_bzip2, create_bzip2 diff --git a/lib/patoolib/programs/lcab.py b/lib/patoolib/programs/lcab.py new file mode 100644 index 000000000..99b071035 --- /dev/null +++ b/lib/patoolib/programs/lcab.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lcab program.""" + +def create_cab (archive, compression, cmd, verbosity, interactive, filenames): + """Create a CAB archive.""" + cmdlist = [cmd, '-r'] + cmdlist.extend(filenames) + cmdlist.append(archive) + return cmdlist diff --git a/lib/patoolib/programs/lha.py b/lib/patoolib/programs/lha.py new file mode 100644 index 000000000..5c78c61ee --- /dev/null +++ b/lib/patoolib/programs/lha.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lha program.""" + +def extract_lzh (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a LZH archive.""" + opts = 'x' + if verbosity > 1: + opts += 'v' + opts += "w=%s" % outdir + return [cmd, opts, archive] + +def list_lzh (archive, compression, cmd, verbosity, interactive): + """List a LZH archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('v') + else: + cmdlist.append('l') + cmdlist.append(archive) + return cmdlist + +def test_lzh (archive, compression, cmd, verbosity, interactive): + """Test a LZH archive.""" + opts = 't' + if verbosity > 1: + opts += 'v' + return [cmd, opts, archive] + +def create_lzh (archive, compression, cmd, verbosity, interactive, filenames): + """Create a LZH archive.""" + opts = 'a' + if verbosity > 1: + opts += 'v' + cmdlist = [cmd, opts, archive] + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/lhasa.py b/lib/patoolib/programs/lhasa.py new file mode 100644 index 000000000..b7a3e816d --- /dev/null +++ b/lib/patoolib/programs/lhasa.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lhasa program.""" +from .lha import extract_lzh + diff --git a/lib/patoolib/programs/lrzip.py b/lib/patoolib/programs/lrzip.py new file mode 100644 index 000000000..d845c219e --- /dev/null +++ b/lib/patoolib/programs/lrzip.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lrzip program.""" +import os +from .. import util + +def extract_lrzip (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a LRZIP archive.""" + cmdlist = [cmd, '-d'] + if verbosity > 1: + cmdlist.append('-v') + outfile = util.get_single_outfile(outdir, archive) + cmdlist.extend(["-o", outfile, os.path.abspath(archive)]) + return cmdlist + +def test_lrzip (archive, compression, cmd, verbosity, interactive): + """Test a LRZIP archive.""" + cmdlist = [cmd, '-t'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.append(archive) + return cmdlist + +def create_lrzip (archive, compression, cmd, verbosity, interactive, filenames): + """Create a LRZIP archive.""" + cmdlist = [cmd, '-o', archive] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/lzip.py b/lib/patoolib/programs/lzip.py new file mode 100644 index 000000000..9b52aa434 --- /dev/null +++ b/lib/patoolib/programs/lzip.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lzip program.""" +from . import extract_singlefile_standard, \ + test_singlefile_standard, create_singlefile_standard + + +extract_lzip = extract_singlefile_standard +test_lzip = test_singlefile_standard +create_lzip = create_singlefile_standard diff --git a/lib/patoolib/programs/lzma.py b/lib/patoolib/programs/lzma.py new file mode 100644 index 000000000..489880451 --- /dev/null +++ b/lib/patoolib/programs/lzma.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lzma program.""" +from . import extract_singlefile_standard, \ + test_singlefile_standard, create_singlefile_standard + + +extract_lzma = extract_singlefile_standard +test_lzma = test_singlefile_standard +create_lzma = create_singlefile_standard diff --git a/lib/patoolib/programs/lzop.py b/lib/patoolib/programs/lzop.py new file mode 100644 index 000000000..964e86ac9 --- /dev/null +++ b/lib/patoolib/programs/lzop.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lzop program.""" +from . import extract_singlefile_standard + + +extract_lzop = extract_singlefile_standard + + +def list_lzop (archive, compression, cmd, verbosity, interactive): + """List a LZOP archive.""" + cmdlist = [cmd, '--list'] + if verbosity > 1: + cmdlist.append('--verbose') + cmdlist.extend(['--', archive]) + return cmdlist + +def test_lzop (archive, compression, cmd, verbosity, interactive): + """Test a LZOP archive.""" + cmdlist = [cmd, '--test'] + if verbosity > 1: + cmdlist.append('--verbose') + cmdlist.extend(['--', archive]) + return cmdlist + +def create_lzop (archive, compression, cmd, verbosity, interactive, filenames): + """Create a LZOP archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-o', archive, '--']) + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/mac.py b/lib/patoolib/programs/mac.py new file mode 100644 index 000000000..7047b2f49 --- /dev/null +++ b/lib/patoolib/programs/mac.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the MAC.exe program.""" +from .. import util + +def extract_ape (archive, compression, cmd, verbosity, interactive, outdir): + """Decompress an APE archive to a WAV file.""" + outfile = util.get_single_outfile(outdir, archive, extension=".wav") + return [cmd, archive, outfile, '-d'] + + +def create_ape (archive, compression, cmd, verbosity, interactive, filenames): + """Compress a WAV file to an APE archive.""" + cmdlist = [cmd] + cmdlist.extend(filenames) + cmdlist.append(archive) + cmdlist.append('-c2000') + return cmdlist + + +def test_ape (archive, compression, cmd, verbosity, interactive): + """Test an APE archive.""" + return [cmd, archive, '-v'] diff --git a/lib/patoolib/programs/nomarch.py b/lib/patoolib/programs/nomarch.py new file mode 100644 index 000000000..1de7b8ca3 --- /dev/null +++ b/lib/patoolib/programs/nomarch.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the nomarch program.""" +import os + +def extract_arc (archive, compression, cmd, verbosity, interactive, outdir): + """Extract an ARC archive.""" + # Since extracted files will be placed in the current directory, + # the cwd argument has to be the output directory. + cmdlist = [cmd, os.path.abspath(archive)] + return (cmdlist, {'cwd': outdir}) + +def list_arc (archive, compression, cmd, verbosity, interactive): + """List an ARC archive.""" + cmdlist = [cmd, '-l'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.append(archive) + return cmdlist + +def test_arc (archive, compression, cmd, verbosity, interactive): + """Test an ARC archive.""" + return [cmd, '-t', archive] diff --git a/lib/patoolib/programs/p7azip.py b/lib/patoolib/programs/p7azip.py new file mode 100644 index 000000000..6f2beb39e --- /dev/null +++ b/lib/patoolib/programs/p7azip.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the 7za program. + +From the man page: +7za is a stand-alone executable. 7za handles less archive formats than 7z, +but does not need any others. +""" + +from .p7zip import \ + extract_bzip2, \ + extract_gzip, \ + extract_zip, \ + extract_compress, \ + extract_rar, \ + extract_cab, \ + extract_7z, \ + list_bzip2, \ + list_gzip, \ + list_zip, \ + list_compress, \ + list_rar, \ + list_cab, \ + list_rpm, \ + list_7z, \ + test_bzip2, \ + test_gzip, \ + test_zip, \ + test_compress, \ + test_rar, \ + test_cab, \ + test_7z, \ + create_7z, \ + create_zip, \ + create_gzip, \ + create_bzip2 diff --git a/lib/patoolib/programs/p7rzip.py b/lib/patoolib/programs/p7rzip.py new file mode 100644 index 000000000..2e4d15f5a --- /dev/null +++ b/lib/patoolib/programs/p7rzip.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2016 Bastian Kleineidam +# +# 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 . +"""Archive commands for the 7zr program. + +7zr is a light executable supporting only the 7z archive format. +""" + +from .p7zip import \ + extract_7z, \ + list_7z, \ + test_7z, \ + create_7z diff --git a/lib/patoolib/programs/p7zip.py b/lib/patoolib/programs/p7zip.py new file mode 100644 index 000000000..dd23c77c7 --- /dev/null +++ b/lib/patoolib/programs/p7zip.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the 7z program.""" + +def extract_7z(archive, compression, cmd, verbosity, interactive, outdir): + """Extract a 7z archive.""" + cmdlist = [cmd, 'x'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-o%s' % outdir, '--', archive]) + return cmdlist + +def extract_7z_singlefile(archive, compression, cmd, verbosity, interactive, outdir): + """Extract a singlefile archive (eg. gzip or bzip2) with '7z e'. + This makes sure a single file and no subdirectories are created, + which would cause errors with patool repack.""" + cmdlist = [cmd, 'e'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-o%s' % outdir, '--', archive]) + return cmdlist + +extract_bzip2 = \ + extract_gzip = \ + extract_compress = \ + extract_xz = \ + extract_lzma = \ + extract_7z_singlefile + +extract_zip = \ + extract_rar = \ + extract_cab = \ + extract_arj = \ + extract_cpio = \ + extract_rpm = \ + extract_deb = \ + extract_iso = \ + extract_vhd = \ + extract_7z + +def list_7z (archive, compression, cmd, verbosity, interactive): + """List a 7z archive.""" + cmdlist = [cmd, 'l'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['--', archive]) + return cmdlist + +list_bzip2 = \ + list_gzip = \ + list_zip = \ + list_compress = \ + list_rar = \ + list_cab = \ + list_arj = \ + list_cpio = \ + list_rpm = \ + list_deb = \ + list_iso = \ + list_xz = \ + list_lzma = \ + list_vhd = \ + list_7z + + +def test_7z (archive, compression, cmd, verbosity, interactive): + """Test a 7z archive.""" + cmdlist = [cmd, 't'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['--', archive]) + return cmdlist + +test_bzip2 = \ + test_gzip = \ + test_zip = \ + test_compress = \ + test_rar = \ + test_cab = \ + test_arj = \ + test_cpio = \ + test_rpm = \ + test_deb = \ + test_iso = \ + test_xz = \ + test_lzma = \ + test_vhd = \ + test_7z + + +def create_7z(archive, compression, cmd, verbosity, interactive, filenames): + """Create a 7z archive.""" + cmdlist = [cmd, 'a'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-t7z', '-mx=9', '--', archive]) + cmdlist.extend(filenames) + return cmdlist + + +def create_zip(archive, compression, cmd, verbosity, interactive, filenames): + """Create a ZIP archive.""" + cmdlist = [cmd, 'a'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-tzip', '-mx=9', '--', archive]) + cmdlist.extend(filenames) + return cmdlist + + +def create_xz(archive, compression, cmd, verbosity, interactive, filenames): + """Create an XZ archive.""" + cmdlist = [cmd, 'a'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-txz', '-mx=9', '--', archive]) + cmdlist.extend(filenames) + return cmdlist + + +def create_gzip(archive, compression, cmd, verbosity, interactive, filenames): + """Create a GZIP archive.""" + cmdlist = [cmd, 'a'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-tgzip', '-mx=9', '--', archive]) + cmdlist.extend(filenames) + return cmdlist + + +def create_bzip2(archive, compression, cmd, verbosity, interactive, filenames): + """Create a BZIP2 archive.""" + cmdlist = [cmd, 'a'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-tbzip2', '-mx=9', '--', archive]) + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/pbzip2.py b/lib/patoolib/programs/pbzip2.py new file mode 100644 index 000000000..e11961e67 --- /dev/null +++ b/lib/patoolib/programs/pbzip2.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the pbzip2 program.""" +# bzip2 and pbzip2 are compatible +from .bzip2 import extract_bzip2, test_bzip2, create_bzip2 diff --git a/lib/patoolib/programs/pdlzip.py b/lib/patoolib/programs/pdlzip.py new file mode 100644 index 000000000..8120f52cb --- /dev/null +++ b/lib/patoolib/programs/pdlzip.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2011-2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the clzip program.""" +from . import extract_singlefile_standard, \ + test_singlefile_standard, create_singlefile_standard + + +extract_lzip = extract_singlefile_standard +test_lzip = test_singlefile_standard +create_lzip = create_singlefile_standard diff --git a/lib/patoolib/programs/pigz.py b/lib/patoolib/programs/pigz.py new file mode 100644 index 000000000..98cc75dd2 --- /dev/null +++ b/lib/patoolib/programs/pigz.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the pigz program.""" +from .gzip import extract_gzip, test_gzip, create_gzip, list_gzip diff --git a/lib/patoolib/programs/plzip.py b/lib/patoolib/programs/plzip.py new file mode 100644 index 000000000..99e1f9b7b --- /dev/null +++ b/lib/patoolib/programs/plzip.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012 Bastian Kleineidam +# +# 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 . +"""Archive commands for the plzip program.""" +from . import extract_singlefile_standard, \ + test_singlefile_standard, create_singlefile_standard + + +extract_lzip = extract_singlefile_standard +test_lzip = test_singlefile_standard +create_lzip = create_singlefile_standard diff --git a/lib/patoolib/programs/py_bz2.py b/lib/patoolib/programs/py_bz2.py new file mode 100644 index 000000000..5a9ac7749 --- /dev/null +++ b/lib/patoolib/programs/py_bz2.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the bz2 Python module.""" +from .. import util +try: + # try external bz2file module with multi-stream support + import bz2file as bz2 +except ImportError: + import bz2 + +# read in 1MB chunks +READ_SIZE_BYTES = 1024*1024 + +def extract_bzip2 (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a BZIP2 archive with the bz2 Python module.""" + targetname = util.get_single_outfile(outdir, archive) + try: + with bz2.BZ2File(archive) as bz2file: + with open(targetname, 'wb') as targetfile: + data = bz2file.read(READ_SIZE_BYTES) + while data: + targetfile.write(data) + data = bz2file.read(READ_SIZE_BYTES) + except Exception as err: + msg = "error extracting %s to %s: %s" % (archive, targetname, err) + raise util.PatoolError(msg) + return None + + +def create_bzip2 (archive, compression, cmd, verbosity, interactive, filenames): + """Create a BZIP2 archive with the bz2 Python module.""" + if len(filenames) > 1: + raise util.PatoolError('multi-file compression not supported in Python bz2') + try: + with bz2.BZ2File(archive, 'wb') as bz2file: + filename = filenames[0] + with open(filename, 'rb') as srcfile: + data = srcfile.read(READ_SIZE_BYTES) + while data: + bz2file.write(data) + data = srcfile.read(READ_SIZE_BYTES) + except Exception as err: + msg = "error creating %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None diff --git a/lib/patoolib/programs/py_echo.py b/lib/patoolib/programs/py_echo.py new file mode 100644 index 000000000..4b6db15f5 --- /dev/null +++ b/lib/patoolib/programs/py_echo.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands echoing data, implemented by the Python print +statement.""" +from __future__ import print_function +from .. import util + + +def list_bzip2 (archive, compression, cmd, verbosity, interactive): + """List a BZIP2 archive.""" + return stripext(cmd, archive, verbosity) + +list_compress = \ + list_lzma = \ + list_xz = \ + list_lzip = \ + list_lrzip = \ + list_rzip = \ + list_bzip2 + +def list_ape (archive, compression, cmd, verbosity, interactive): + """List an APE archive.""" + return stripext(cmd, archive, verbosity, extension=".wav") + +list_shn = \ + list_flac = \ + list_ape + +def stripext (cmd, archive, verbosity, extension=""): + """Print the name without suffix.""" + if verbosity >= 0: + print(util.stripext(archive)+extension) + return None diff --git a/lib/patoolib/programs/py_gzip.py b/lib/patoolib/programs/py_gzip.py new file mode 100644 index 000000000..149a6b7fc --- /dev/null +++ b/lib/patoolib/programs/py_gzip.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the gzip Python module.""" +from __future__ import absolute_import +# now gzip refers to the Python standard module, not the local one +import gzip +from .. import util + +READ_SIZE_BYTES = 1024*1024 + +def extract_gzip (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a GZIP archive with the gzip Python module.""" + targetname = util.get_single_outfile(outdir, archive) + try: + with gzip.GzipFile(archive) as gzipfile: + with open(targetname, 'wb') as targetfile: + data = gzipfile.read(READ_SIZE_BYTES) + while data: + targetfile.write(data) + data = gzipfile.read(READ_SIZE_BYTES) + except Exception as err: + msg = "error extracting %s to %s: %s" % (archive, targetname, err) + raise util.PatoolError(msg) + return None + + +def create_gzip (archive, compression, cmd, verbosity, interactive, filenames): + """Create a GZIP archive with the gzip Python module.""" + if len(filenames) > 1: + raise util.PatoolError('multi-file compression not supported in Python gzip') + try: + with gzip.GzipFile(archive, 'wb') as gzipfile: + filename = filenames[0] + with open(filename, 'rb') as srcfile: + data = srcfile.read(READ_SIZE_BYTES) + while data: + gzipfile.write(data) + data = srcfile.read(READ_SIZE_BYTES) + except Exception as err: + msg = "error creating %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None diff --git a/lib/patoolib/programs/py_lzma.py b/lib/patoolib/programs/py_lzma.py new file mode 100644 index 000000000..f12cf3ec5 --- /dev/null +++ b/lib/patoolib/programs/py_lzma.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the lzma Python module.""" +from .. import util +import lzma + +READ_SIZE_BYTES = 1024*1024 + +def _extract(archive, compression, cmd, format, verbosity, outdir): + """Extract an LZMA or XZ archive with the lzma Python module.""" + targetname = util.get_single_outfile(outdir, archive) + try: + with lzma.LZMAFile(archive, format=format) as lzmafile: + with open(targetname, 'wb') as targetfile: + data = lzmafile.read(READ_SIZE_BYTES) + while data: + targetfile.write(data) + data = lzmafile.read(READ_SIZE_BYTES) + except Exception as err: + msg = "error extracting %s to %s: %s" % (archive, targetname, err) + raise util.PatoolError(msg) + return None + +def extract_lzma(archive, compression, cmd, verbosity, interactive, outdir): + """Extract an LZMA archive with the lzma Python module.""" + return _extract(archive, compression, cmd, lzma.FORMAT_ALONE, verbosity, outdir) + +def extract_xz(archive, compression, cmd, verbosity, interactive, outdir): + """Extract an XZ archive with the lzma Python module.""" + return _extract(archive, compression, cmd, lzma.FORMAT_XZ, verbosity, outdir) + + +def _create(archive, compression, cmd, format, verbosity, filenames): + """Create an LZMA or XZ archive with the lzma Python module.""" + if len(filenames) > 1: + raise util.PatoolError('multi-file compression not supported in Python lzma') + try: + with lzma.LZMAFile(archive, mode='wb', format=format, preset=9) as lzmafile: + filename = filenames[0] + with open(filename, 'rb') as srcfile: + data = srcfile.read(READ_SIZE_BYTES) + while data: + lzmafile.write(data) + data = srcfile.read(READ_SIZE_BYTES) + except Exception as err: + msg = "error creating %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None + +def create_lzma(archive, compression, cmd, verbosity, interactive, filenames): + """Create an LZMA archive with the lzma Python module.""" + return _create(archive, compression, cmd, lzma.FORMAT_ALONE, verbosity, filenames) + +def create_xz(archive, compression, cmd, verbosity, interactive, filenames): + """Create an XZ archive with the lzma Python module.""" + return _create(archive, compression, cmd, lzma.FORMAT_XZ, verbosity, filenames) diff --git a/lib/patoolib/programs/py_tarfile.py b/lib/patoolib/programs/py_tarfile.py new file mode 100644 index 000000000..d0304f08f --- /dev/null +++ b/lib/patoolib/programs/py_tarfile.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the tarfile Python module.""" +from .. import util, py_lzma +import tarfile + +READ_SIZE_BYTES = 1024*1024 + + +def list_tar (archive, compression, cmd, verbosity, interactive): + """List a TAR archive with the tarfile Python module.""" + try: + with tarfile.open(archive) as tfile: + tfile.list(verbose=verbosity>1) + except Exception as err: + msg = "error listing %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None + +test_tar = list_tar + +def extract_tar (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a TAR archive with the tarfile Python module.""" + try: + with tarfile.open(archive) as tfile: + tfile.extractall(path=outdir) + except Exception as err: + msg = "error extracting %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None + + +def create_tar (archive, compression, cmd, verbosity, interactive, filenames): + """Create a TAR archive with the tarfile Python module.""" + mode = get_tar_mode(compression) + try: + with tarfile.open(archive, mode) as tfile: + for filename in filenames: + tfile.add(filename) + except Exception as err: + msg = "error creating %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None + + +def get_tar_mode (compression): + """Determine tarfile open mode according to the given compression.""" + if compression == 'gzip': + return 'w:gz' + if compression == 'bzip2': + return 'w:bz2' + if compression == 'lzma' and py_lzma: + return 'w:xz' + if compression: + msg = 'pytarfile does not support %s for tar compression' + raise util.PatoolError(msg % compression) + # no compression + return 'w' diff --git a/lib/patoolib/programs/py_zipfile.py b/lib/patoolib/programs/py_zipfile.py new file mode 100644 index 000000000..73daf9878 --- /dev/null +++ b/lib/patoolib/programs/py_zipfile.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the zipfile Python module.""" +from __future__ import print_function +from .. import util +import zipfile +import os + +READ_SIZE_BYTES = 1024*1024 + + +def list_zip(archive, compression, cmd, verbosity, interactive): + """List member of a ZIP archive with the zipfile Python module.""" + try: + with zipfile.ZipFile(archive, "r") as zfile: + for name in zfile.namelist(): + if verbosity >= 0: + print(name) + except Exception as err: + msg = "error listing %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None + +test_zip = list_zip + +def extract_zip(archive, compression, cmd, verbosity, interactive, outdir): + """Extract a ZIP archive with the zipfile Python module.""" + try: + with zipfile.ZipFile(archive) as zfile: + zfile.extractall(outdir) + except Exception as err: + msg = "error extracting %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None + + +def create_zip(archive, compression, cmd, verbosity, interactive, filenames): + """Create a ZIP archive with the zipfile Python module.""" + try: + with zipfile.ZipFile(archive, 'w') as zfile: + for filename in filenames: + if os.path.isdir(filename): + write_directory(zfile, filename) + else: + zfile.write(filename) + except Exception as err: + msg = "error creating %s: %s" % (archive, err) + raise util.PatoolError(msg) + return None + + +def write_directory (zfile, directory): + """Write recursively all directories and filenames to zipfile instance.""" + for dirpath, dirnames, filenames in os.walk(directory): + zfile.write(dirpath) + for filename in filenames: + zfile.write(os.path.join(dirpath, filename)) diff --git a/lib/patoolib/programs/rar.py b/lib/patoolib/programs/rar.py new file mode 100644 index 000000000..b635e7cbf --- /dev/null +++ b/lib/patoolib/programs/rar.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the rar program.""" +import os + +def extract_rar (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a RAR archive.""" + cmdlist = [cmd, 'x'] + if not interactive: + cmdlist.extend(['-p-', '-y']) + cmdlist.extend(['--', os.path.abspath(archive)]) + return (cmdlist, {'cwd': outdir}) + +def list_rar (archive, compression, cmd, verbosity, interactive): + """List a RAR archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('v') + else: + cmdlist.append('l') + if not interactive: + cmdlist.extend(['-p-', '-y']) + cmdlist.extend(['--', archive]) + return cmdlist + +def test_rar (archive, compression, cmd, verbosity, interactive): + """Test a RAR archive.""" + cmdlist = [cmd, 't'] + if not interactive: + cmdlist.extend(['-p-', '-y']) + cmdlist.extend(['--', archive]) + return cmdlist + +def create_rar (archive, compression, cmd, verbosity, interactive, filenames): + """Create a RAR archive.""" + cmdlist = [cmd, 'a'] + if not interactive: + cmdlist.append('-y') + cmdlist.extend(['-r', '-m5', '--', archive]) + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/rpm.py b/lib/patoolib/programs/rpm.py new file mode 100644 index 000000000..5f5aa6854 --- /dev/null +++ b/lib/patoolib/programs/rpm.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the rpm program.""" + +def list_rpm (archive, compression, cmd, verbosity, interactive): + """List a RPM archive.""" + cmdlist = [cmd, '-q', '-l'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-p', '--', archive]) + return cmdlist + +def test_rpm (archive, compression, cmd, verbosity, interactive): + """Test a RPM archive.""" + cmdlist = [cmd, 'V'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-p', '--', archive]) + return cmdlist diff --git a/lib/patoolib/programs/rpm2cpio.py b/lib/patoolib/programs/rpm2cpio.py new file mode 100644 index 000000000..14e53086e --- /dev/null +++ b/lib/patoolib/programs/rpm2cpio.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the rpm2cpio program.""" +import os +from .. import util + +def extract_rpm (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a RPM archive.""" + # also check cpio + cpio = util.find_program("cpio") + if not cpio: + raise util.PatoolError("cpio(1) is required for rpm2cpio extraction; please install it") + path = util.shell_quote(os.path.abspath(archive)) + cmdlist = [util.shell_quote(cmd), path, "|", util.shell_quote(cpio), + '--extract', '--make-directories', '--preserve-modification-time', + '--no-absolute-filenames', '--force-local', '--nonmatching', + r'"*\.\.*"'] + if verbosity > 1: + cmdlist.append('-v') + return (cmdlist, {'cwd': outdir, 'shell': True}) diff --git a/lib/patoolib/programs/rzip.py b/lib/patoolib/programs/rzip.py new file mode 100644 index 000000000..48608e1d8 --- /dev/null +++ b/lib/patoolib/programs/rzip.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the rzip program.""" +from .. import util + +def extract_rzip (archive, compression, cmd, verbosity, interactive, outdir): + """Extract an RZIP archive.""" + cmdlist = [cmd, '-d', '-k'] + if verbosity > 1: + cmdlist.append('-v') + outfile = util.get_single_outfile(outdir, archive) + cmdlist.extend(["-o", outfile, archive]) + return cmdlist + +def create_rzip (archive, compression, cmd, verbosity, interactive, filenames): + """Create an RZIP archive.""" + cmdlist = [cmd, '-k', '-9', '-o', archive] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/shar.py b/lib/patoolib/programs/shar.py new file mode 100644 index 000000000..f13175e74 --- /dev/null +++ b/lib/patoolib/programs/shar.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the shar program.""" +from .. import util + +def create_shar (archive, compression, cmd, verbosity, interactive, filenames): + """Create a SHAR archive.""" + cmdlist = [util.shell_quote(cmd)] + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/shorten.py b/lib/patoolib/programs/shorten.py new file mode 100644 index 000000000..635e49de7 --- /dev/null +++ b/lib/patoolib/programs/shorten.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the shorten program.""" +from .. import util + +def extract_shn (archive, compression, cmd, verbosity, interactive, outdir): + """Decompress a SHN archive to a WAV file.""" + cmdlist = [util.shell_quote(cmd)] + outfile = util.get_single_outfile(outdir, archive, extension=".wav") + cmdlist.extend(['-x', '-', util.shell_quote(outfile), '<', + util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) + + +def create_shn (archive, compression, cmd, verbosity, interactive, filenames): + """Compress a WAV file to a SHN archive.""" + if len(filenames) > 1: + raise util.PatoolError("multiple filenames for shorten not supported") + cmdlist = [util.shell_quote(cmd)] + cmdlist.extend(['-', util.shell_quote(archive), '<', + util.shell_quote(filenames[0])]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/star.py b/lib/patoolib/programs/star.py new file mode 100644 index 000000000..51a8262eb --- /dev/null +++ b/lib/patoolib/programs/star.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the star program.""" +from .tar import add_tar_opts as add_star_opts + +def extract_tar (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a TAR archive.""" + cmdlist = [cmd, '-x'] + add_star_opts(cmdlist, compression, verbosity) + cmdlist.extend(['-C', outdir, 'file=%s' % archive]) + return cmdlist + +def list_tar (archive, compression, cmd, verbosity, interactive): + """List a TAR archive.""" + cmdlist = [cmd, '-n'] + add_star_opts(cmdlist, compression, verbosity) + cmdlist.append("file=%s" % archive) + return cmdlist + +test_tar = list_tar + +def create_tar (archive, compression, cmd, verbosity, interactive, filenames): + """Create a TAR archive.""" + cmdlist = [cmd, '-c'] + add_star_opts(cmdlist, compression, verbosity) + cmdlist.append("file=%s" % archive) + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/tar.py b/lib/patoolib/programs/tar.py new file mode 100644 index 000000000..d8d1d361e --- /dev/null +++ b/lib/patoolib/programs/tar.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the GNU tar program.""" +import os + + +def extract_tar (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a TAR archive.""" + cmdlist = [cmd, '--extract'] + add_tar_opts(cmdlist, compression, verbosity) + cmdlist.extend(["--file", archive, '--directory', outdir]) + return cmdlist + +def list_tar (archive, compression, cmd, verbosity, interactive): + """List a TAR archive.""" + cmdlist = [cmd, '--list'] + add_tar_opts(cmdlist, compression, verbosity) + cmdlist.extend(["--file", archive]) + return cmdlist + +test_tar = list_tar + +def create_tar (archive, compression, cmd, verbosity, interactive, filenames): + """Create a TAR archive.""" + cmdlist = [cmd, '--create'] + add_tar_opts(cmdlist, compression, verbosity) + cmdlist.extend(["--file", archive, '--']) + cmdlist.extend(filenames) + return cmdlist + +def add_tar_opts (cmdlist, compression, verbosity): + """Add tar options to cmdlist.""" + progname = os.path.basename(cmdlist[0]) + if compression == 'gzip': + cmdlist.append('-z') + elif compression == 'compress': + cmdlist.append('-Z') + elif compression == 'bzip2': + cmdlist.append('-j') + elif compression in ('lzma', 'xz') and progname == 'bsdtar': + cmdlist.append('--%s' % compression) + elif compression in ('lzma', 'xz', 'lzip'): + # use the compression name as program name since + # tar is picky which programs it can use + program = compression + # set compression program + cmdlist.extend(['--use-compress-program', program]) + if verbosity > 1: + cmdlist.append('--verbose') diff --git a/lib/patoolib/programs/unace.py b/lib/patoolib/programs/unace.py new file mode 100644 index 000000000..845b985aa --- /dev/null +++ b/lib/patoolib/programs/unace.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the unace program.""" + +def extract_ace (archive, compression, cmd, verbosity, interactive, outdir): + """Extract an ACE archive.""" + cmdlist = [cmd, 'x'] + if not outdir.endswith('/'): + outdir += '/' + cmdlist.extend([archive, outdir]) + return cmdlist + +def list_ace (archive, compression, cmd, verbosity, interactive): + """List an ACE archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('v') + else: + cmdlist.append('l') + cmdlist.append(archive) + return cmdlist + +def test_ace (archive, compression, cmd, verbosity, interactive): + """Test an ACE archive.""" + return [cmd, 't', archive] diff --git a/lib/patoolib/programs/unadf.py b/lib/patoolib/programs/unadf.py new file mode 100644 index 000000000..496cbd687 --- /dev/null +++ b/lib/patoolib/programs/unadf.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the unadf program.""" + + +def extract_adf (archive, compression, cmd, verbosity, interactive, outdir): + """Extract an ADF archive.""" + return [cmd, archive, '-d', outdir] + + +def list_adf (archive, compression, cmd, verbosity, interactive): + """List an ADF archive.""" + return [cmd, '-l', archive] + +test_adf = list_adf diff --git a/lib/patoolib/programs/unalz.py b/lib/patoolib/programs/unalz.py new file mode 100644 index 000000000..4255f2640 --- /dev/null +++ b/lib/patoolib/programs/unalz.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the unalz program.""" + +def extract_alzip (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a ALZIP archive.""" + return [cmd, '-d', outdir, archive] + + +def list_alzip (archive, compression, cmd, verbosity, interactive): + """List a ALZIP archive.""" + return [cmd, '-l', archive] + +test_alzip = list_alzip diff --git a/lib/patoolib/programs/uncompress.py b/lib/patoolib/programs/uncompress.py new file mode 100644 index 000000000..09f8838e3 --- /dev/null +++ b/lib/patoolib/programs/uncompress.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the uncompress.real program.""" +from .. import util + + +def extract_compress (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a compressed archive.""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + outfile = util.get_single_outfile(outdir, archive) + cmdlist.extend(['-c', util.shell_quote(archive), '>', + util.shell_quote(outfile)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/unrar.py b/lib/patoolib/programs/unrar.py new file mode 100644 index 000000000..490bbbe76 --- /dev/null +++ b/lib/patoolib/programs/unrar.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the unrar program.""" + +from .rar import extract_rar, list_rar, test_rar diff --git a/lib/patoolib/programs/unshar.py b/lib/patoolib/programs/unshar.py new file mode 100644 index 000000000..e5b757566 --- /dev/null +++ b/lib/patoolib/programs/unshar.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2012-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the unshar program.""" +import os + +def extract_shar (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a SHAR archive.""" + cmdlist = [cmd, os.path.abspath(archive)] + return (cmdlist, {'cwd': outdir}) diff --git a/lib/patoolib/programs/unzip.py b/lib/patoolib/programs/unzip.py new file mode 100644 index 000000000..1768f72ac --- /dev/null +++ b/lib/patoolib/programs/unzip.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the unzip program.""" + +def extract_zip (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a ZIP archive.""" + cmdlist = [cmd] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['--', archive, '-d', outdir]) + return cmdlist + +def list_zip (archive, compression, cmd, verbosity, interactive): + """List a ZIP archive.""" + cmdlist = [cmd, '-l'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['--', archive]) + return cmdlist + +def test_zip (archive, compression, cmd, verbosity, interactive): + """Test a ZIP archive.""" + cmdlist = [cmd, '-t'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['--', archive]) + return cmdlist diff --git a/lib/patoolib/programs/xdms.py b/lib/patoolib/programs/xdms.py new file mode 100644 index 000000000..c1935f367 --- /dev/null +++ b/lib/patoolib/programs/xdms.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2011-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the xdms program.""" +from .. import util + + +def extract_dms (archive, compression, cmd, verbosity, interactive, outdir): + """Extract a DMS archive.""" + check_archive_ext(archive) + cmdlist = [cmd, '-d', outdir] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['u', archive]) + return cmdlist + + +def list_dms (archive, compression, cmd, verbosity, interactive): + """List a DMS archive.""" + check_archive_ext(archive) + return [cmd, 'v', archive] + + +def test_dms (archive, compression, cmd, verbosity, interactive): + """Test a DMS archive.""" + check_archive_ext(archive) + return [cmd, 't', archive] + + +def check_archive_ext (archive): + """xdms(1) cannot handle files with extensions other than '.dms'.""" + if not archive.lower().endswith(".dms"): + rest = archive[-4:] + msg = "xdms(1) archive file must end with `.dms', not `%s'" % rest + raise util.PatoolError(msg) diff --git a/lib/patoolib/programs/xz.py b/lib/patoolib/programs/xz.py new file mode 100644 index 000000000..b27c475ab --- /dev/null +++ b/lib/patoolib/programs/xz.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the xz program.""" +from . import extract_singlefile_standard, test_singlefile_standard +from .. import util + + +extract_xz = extract_singlefile_standard +test_xz = test_singlefile_standard + +def list_xz (archive, compression, cmd, verbosity, interactive): + """List a XZ archive.""" + cmdlist = [cmd] + cmdlist.append('-l') + if verbosity > 1: + cmdlist.append('-v') + cmdlist.append(archive) + return cmdlist + + +def create_xz(archive, compression, cmd, verbosity, interactive, filenames): + """Create an XZ archive.""" + cmdlist = [util.shell_quote(cmd)] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-c', '-9', '--']) + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) + + +def extract_lzma(archive, compression, cmd, verbosity, interactive, outdir): + """Extract an LZMA archive.""" + cmdlist = [util.shell_quote(cmd), '--format=lzma'] + if verbosity > 1: + cmdlist.append('-v') + outfile = util.get_single_outfile(outdir, archive) + cmdlist.extend(['-c', '-d', '--', util.shell_quote(archive), '>', + util.shell_quote(outfile)]) + return (cmdlist, {'shell': True}) + + +def test_lzma(archive, compression, cmd, verbosity, interactive): + """Test an LZMA archive.""" + cmdlist = [cmd, '--format=lzma'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['--test', archive]) + return cmdlist + + +def create_lzma(archive, compression, cmd, verbosity, interactive, filenames): + """Create an LZMA archive.""" + cmdlist = [util.shell_quote(cmd), '--format=lzma'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.extend(['-c', '-9', '--']) + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/zip.py b/lib/patoolib/programs/zip.py new file mode 100644 index 000000000..920261698 --- /dev/null +++ b/lib/patoolib/programs/zip.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the zip program.""" + +def create_zip (archive, compression, cmd, verbosity, interactive, filenames): + """Create a ZIP archive.""" + cmdlist = [cmd, '-r', '-9'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.append(archive) + cmdlist.extend(filenames) + return cmdlist + +def test_zip (archive, compression, cmd, verbosity, interactive): + """Test a ZIP archive.""" + cmdlist = [cmd, '--test'] + if verbosity > 1: + cmdlist.append('-v') + cmdlist.append(archive) + return cmdlist diff --git a/lib/patoolib/programs/zoo.py b/lib/patoolib/programs/zoo.py new file mode 100644 index 000000000..2bfe82270 --- /dev/null +++ b/lib/patoolib/programs/zoo.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the zoo program.""" +import os + +def extract_zoo(archive, compression, cmd, verbosity, interactive, outdir): + """Extract a ZOO archive.""" + # Since extracted files will be placed in the current directory, + # the cwd argument has to be the output directory. + cmdlist = [cmd, '-extract', os.path.abspath(archive)] + return (cmdlist, {'cwd': outdir}) + + +def list_zoo(archive, compression, cmd, verbosity, interactive): + """List a ZOO archive.""" + return [cmd, '-list', archive] + + +def test_zoo(archive, compression, cmd, verbosity, interactive): + """Test a ZOO archive.""" + return [cmd, '-test', archive] + + +def create_zoo(archive, compression, cmd, verbosity, interactive, filenames): + """Create a ZOO archive.""" + cmdlist = [cmd, '-add', archive] + cmdlist.extend(filenames) + return cmdlist diff --git a/lib/patoolib/programs/zopfli.py b/lib/patoolib/programs/zopfli.py new file mode 100644 index 000000000..bea985503 --- /dev/null +++ b/lib/patoolib/programs/zopfli.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2013-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the zopfli program.""" +from .. import util + +def create_gzip(archive, compression, cmd, verbosity, interactive, filenames): + """Create a GZIP archive.""" + cmdlist = [util.shell_quote(cmd)] + cmdlist.extend(['-c', '--']) + cmdlist.extend([util.shell_quote(x) for x in filenames]) + cmdlist.extend(['>', util.shell_quote(archive)]) + return (cmdlist, {'shell': True}) diff --git a/lib/patoolib/programs/zpaq.py b/lib/patoolib/programs/zpaq.py new file mode 100644 index 000000000..795cf5bef --- /dev/null +++ b/lib/patoolib/programs/zpaq.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2015 Bastian Kleineidam +# +# 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 . +"""Archive commands for the zpaq program.""" +import os + +def extract_zpaq(archive, compression, cmd, verbosity, interactive, outdir): + """Extract a ZPAQ archive.""" + cmdlist = [cmd, 'x', os.path.abspath(archive)] + return (cmdlist, {'cwd': outdir}) + + +def list_zpaq(archive, compression, cmd, verbosity, interactive): + """List a ZPAQ archive.""" + return [cmd, 'l', archive] + + +def create_zpaq(archive, compression, cmd, verbosity, interactive, filenames): + """Create a ZPAQ archive.""" + cmdlist = [cmd, 'a', archive] + cmdlist.extend(filenames) + cmdlist.extend(['-method', '4']) + return cmdlist + +# zpaq has no separate test mode, so use listing instead +test_zpaq = list_zpaq + diff --git a/lib/patoolib/util.py b/lib/patoolib/util.py new file mode 100644 index 000000000..5975e7592 --- /dev/null +++ b/lib/patoolib/util.py @@ -0,0 +1,710 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2016 Bastian Kleineidam +# +# 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 . +"""Utility functions.""" +from __future__ import print_function +import os +import sys +import shutil +import subprocess +import mimetypes +import tempfile +import time +import traceback +import locale +from . import configuration, ArchiveMimetypes, ArchiveCompressions +try: + from shutil import which +except ImportError: + # from Python 3.3 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + """ + def _access_check(fn, mode): + """Check that a given file can be accessed with the correct mode. + Additionally check that `fn` is not a directory, as on Windows + directories pass the os.access check.""" + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + path = (path or os.environ.get("PATH", os.defpath)).split(os.pathsep) + if sys.platform == "win32": + # The current directory takes precedence on Windows. + if not os.curdir in path: + path.insert(0, os.curdir) + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if not normdir in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None + + +def system_search_path(): + """Get the list of directories on a system to search for executable programs. + It is either the PATH environment variable or if PATH is undefined the value + of os.defpath. + """ + return os.environ.get("PATH", os.defpath) + + +# internal MIME database +mimedb = None + +def init_mimedb(): + """Initialize the internal MIME database.""" + global mimedb + try: + mimedb = mimetypes.MimeTypes(strict=False) + except Exception as msg: + log_error("could not initialize MIME database: %s" % msg) + return + add_mimedb_data(mimedb) + + +def add_mimedb_data(mimedb): + """Add missing encodings and mimetypes to MIME database.""" + mimedb.encodings_map['.bz2'] = 'bzip2' + mimedb.encodings_map['.lzma'] = 'lzma' + mimedb.encodings_map['.xz'] = 'xz' + mimedb.encodings_map['.lz'] = 'lzip' + mimedb.suffix_map['.tbz2'] = '.tar.bz2' + add_mimetype(mimedb, 'application/x-lzop', '.lzo') + add_mimetype(mimedb, 'application/x-adf', '.adf') + add_mimetype(mimedb, 'application/x-arj', '.arj') + add_mimetype(mimedb, 'application/x-lzma', '.lzma') + add_mimetype(mimedb, 'application/x-xz', '.xz') + add_mimetype(mimedb, 'application/java-archive', '.jar') + add_mimetype(mimedb, 'application/x-rar', '.rar') + add_mimetype(mimedb, 'application/x-rar', '.cbr') + add_mimetype(mimedb, 'application/x-7z-compressed', '.7z') + add_mimetype(mimedb, 'application/x-7z-compressed', '.cb7') + add_mimetype(mimedb, 'application/x-cab', '.cab') + add_mimetype(mimedb, 'application/x-rpm', '.rpm') + add_mimetype(mimedb, 'application/x-debian-package', '.deb') + add_mimetype(mimedb, 'application/x-ace', '.ace') + add_mimetype(mimedb, 'application/x-ace', '.cba') + add_mimetype(mimedb, 'application/x-archive', '.a') + add_mimetype(mimedb, 'application/x-alzip', '.alz') + add_mimetype(mimedb, 'application/x-arc', '.arc') + add_mimetype(mimedb, 'application/x-lrzip', '.lrz') + add_mimetype(mimedb, 'application/x-lha', '.lha') + add_mimetype(mimedb, 'application/x-lzh', '.lzh') + add_mimetype(mimedb, 'application/x-rzip', '.rz') + add_mimetype(mimedb, 'application/x-zoo', '.zoo') + add_mimetype(mimedb, 'application/x-dms', '.dms') + add_mimetype(mimedb, 'application/x-zip-compressed', '.crx') + add_mimetype(mimedb, 'application/x-shar', '.shar') + add_mimetype(mimedb, 'application/x-tar', '.cbt') + add_mimetype(mimedb, 'application/x-vhd', '.vhd') + add_mimetype(mimedb, 'audio/x-ape', '.ape') + add_mimetype(mimedb, 'audio/x-shn', '.shn') + add_mimetype(mimedb, 'audio/flac', '.flac') + add_mimetype(mimedb, 'application/x-chm', '.chm') + add_mimetype(mimedb, 'application/x-iso9660-image', '.iso') + add_mimetype(mimedb, 'application/zip', '.cbz') + add_mimetype(mimedb, 'application/zip', '.epub') + add_mimetype(mimedb, 'application/zip', '.apk') + add_mimetype(mimedb, 'application/zpaq', '.zpaq') + + +def add_mimetype(mimedb, mimetype, extension): + """Add or replace a mimetype to be used with the given extension.""" + # If extension is already a common type, strict=True must be used. + strict = extension in mimedb.types_map[True] + mimedb.add_type(mimetype, extension, strict=strict) + + +class PatoolError (Exception): + """Raised when errors occur.""" + pass + + +class memoized (object): + """Decorator that caches a function's return value each time it is called. + If called later with the same arguments, the cached value is returned, and + not re-evaluated.""" + + def __init__(self, func): + """Set func and init cache.""" + self.func = func + self.cache = {} + + def __call__(self, *args): + """Try to find result for function arguments in local cache or + execute the function and fill the cache with the result.""" + try: + return self.cache[args] + except KeyError: + self.cache[args] = value = self.func(*args) + return value + except TypeError: + # uncachable -- for instance, passing a list as an argument. + # Better to not cache than to blow up entirely. + return self.func(*args) + + def __repr__(self): + """Return the function's docstring.""" + return self.func.__doc__ + + +def backtick (cmd, encoding='utf-8'): + """Return decoded output from command.""" + data = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] + return data.decode(encoding) + + +def run (cmd, verbosity=0, **kwargs): + """Run command without error checking. + @return: command return code""" + # Note that shell_quote_nt() result is not suitable for copy-paste + # (especially on Unix systems), but it looks nicer than shell_quote(). + if verbosity >= 0: + log_info("running %s" % " ".join(map(shell_quote_nt, cmd))) + if kwargs: + if verbosity >= 0: + log_info(" with %s" % ", ".join("%s=%s" % (k, shell_quote(str(v)))\ + for k, v in kwargs.items())) + if kwargs.get("shell"): + # for shell calls the command must be a string + cmd = " ".join(cmd) + if verbosity < 1: + # hide command output on stdout + with open(os.devnull, 'wb') as devnull: + kwargs['stdout'] = devnull + res = subprocess.call(cmd, **kwargs) + else: + res = subprocess.call(cmd, **kwargs) + return res + + +def run_checked (cmd, ret_ok=(0,), **kwargs): + """Run command and raise PatoolError on error.""" + retcode = run(cmd, **kwargs) + if retcode not in ret_ok: + msg = "Command `%s' returned non-zero exit status %d" % (cmd, retcode) + raise PatoolError(msg) + return retcode + + +@memoized +def guess_mime (filename): + """Guess the MIME type of given filename using file(1) and if that + fails by looking at the filename extension with the Python mimetypes + module. + + The result of this function is cached. + """ + mime, encoding = guess_mime_file(filename) + if mime is None: + mime, encoding = guess_mime_mimedb(filename) + assert mime is not None or encoding is None + return mime, encoding + + +Encoding2Mime = { + 'gzip': "application/gzip", + 'bzip2': "application/x-bzip2", + 'compress': "application/x-compress", + 'lzma': "application/x-lzma", + 'lzip': "application/x-lzip", + 'xz': "application/x-xz", +} +Mime2Encoding = dict([(_val, _key) for _key, _val in Encoding2Mime.items()]) +# libmagic before version 5.14 identified .gz files as application/x-gzip +Mime2Encoding['application/x-gzip'] = 'gzip' + + +def guess_mime_mimedb (filename): + """Guess MIME type from given filename. + @return: tuple (mime, encoding) + """ + mime, encoding = None, None + if mimedb is not None: + mime, encoding = mimedb.guess_type(filename, strict=False) + if mime not in ArchiveMimetypes and encoding in ArchiveCompressions: + # Files like 't.txt.gz' are recognized with encoding as format, and + # an unsupported mime-type like 'text/plain'. Fix this. + mime = Encoding2Mime[encoding] + encoding = None + return mime, encoding + + +def guess_mime_file (filename): + """Determine MIME type of filename with file(1): + (a) using `file --mime` + (b) using `file` and look the result string + @return: tuple (mime, encoding) + """ + mime, encoding = None, None + base, ext = os.path.splitext(filename) + if ext.lower() in ('.alz',): + # let mimedb recognize these extensions + return mime, encoding + if os.path.isfile(filename): + file_prog = find_program("file") + if file_prog: + mime, encoding = guess_mime_file_mime(file_prog, filename) + if mime is None: + mime = guess_mime_file_text(file_prog, filename) + encoding = None + if mime in Mime2Encoding: + # try to look inside compressed archives + cmd = [file_prog, "--brief", "--mime", "--uncompress", filename] + try: + outparts = backtick(cmd).strip().split(";") + except OSError: + # ignore errors, as file(1) is only a fallback + return mime, encoding + mime2 = outparts[0].split(" ", 1)[0] + # Some file(1) implementations return an empty or unknown mime type + # when the uncompressor program is not installed, other + # implementation return the original file type. + # The following detects both cases. + if (mime2 in ('application/x-empty', 'application/octet-stream') or + mime2 in Mime2Encoding): + # The uncompressor program file(1) uses is not installed + # or is not able to uncompress. + # Try to get mime information from the file extension. + mime2, encoding2 = guess_mime_mimedb(filename) + if mime2 in ArchiveMimetypes: + mime = mime2 + encoding = encoding2 + elif mime2 in ArchiveMimetypes: + mime = mime2 + encoding = get_file_mime_encoding(outparts) + return mime, encoding + + +def guess_mime_file_mime (file_prog, filename): + """Determine MIME type of filename with file(1) and --mime option. + @return: tuple (mime, encoding) + """ + mime, encoding = None, None + cmd = [file_prog, "--brief", "--mime-type", filename] + try: + mime = backtick(cmd).strip() + except OSError: + # ignore errors, as file(1) is only a fallback + pass + if mime not in ArchiveMimetypes: + mime, encoding = None, None + return mime, encoding + + +def get_file_mime_encoding (parts): + """Get encoding value from splitted output of file --mime --uncompress.""" + for part in parts: + for subpart in part.split(" "): + if subpart.startswith("compressed-encoding="): + mime = subpart.split("=")[1].strip() + return Mime2Encoding.get(mime) + return None + + +# Match file(1) output text to mime types +FileText2Mime = { + "7-zip archive data": "application/x-7z-compressed", + "ACE archive data": "application/x-ace", + "Amiga DOS disk": "application/x-adf", + "ARJ archive data": "application/x-arj", + "bzip2 compressed data": "application/x-bzip2", + "cpio archive": "application/x-cpio", + "ASCII cpio archive": "application/x-cpio", + "Debian binary package": "application/x-debian-package", + "gzip compressed data": "application/x-gzip", + "LZMA compressed data": "application/x-lzma", + "LRZIP compressed data": "application/x-lrzip", + "lzop compressed data": "application/x-lzop", + "Microsoft Cabinet archive data": "application/vnd.ms-cab-compressed", + "RAR archive data": "application/x-rar", + "RPM ": "application/x-redhat-package-manager", + "POSIX tar archive": "application/x-tar", + "xz compressed data": "application/x-xz", + "Zip archive data": "application/zip", + "compress'd data": "application/x-compress", + "lzip compressed data": "application/x-lzip", + "rzip compressed data": "application/x-rzip", + "current ar archive": "application/x-archive", + "LHa ": "application/x-lha", + "ARC archive data": "application/x-arc", + "Zoo archive data": "application/x-zoo", + "DMS archive data": "application/x-dms", + "Monkey's Audio": "audio/x-ape", + "FLAC audio bitstream data": "audio/flac", + "MS Windows HtmlHelp Data": "application/x-chm", + "ZPAQ stream": "application/zpaq", +} + +def guess_mime_file_text (file_prog, filename): + """Determine MIME type of filename with file(1).""" + cmd = [file_prog, "--brief", filename] + try: + output = backtick(cmd).strip() + except OSError: + # ignore errors, as file(1) is only a fallback + return None + # match output against known strings + for matcher, mime in FileText2Mime.items(): + if output.startswith(matcher) and mime in ArchiveMimetypes: + return mime + return None + + +def check_existing_filename (filename, onlyfiles=True): + """Ensure that given filename is a valid, existing file.""" + if not os.path.exists(filename): + raise PatoolError("file `%s' was not found" % filename) + if not os.access(filename, os.R_OK): + raise PatoolError("file `%s' is not readable" % filename) + if onlyfiles and not os.path.isfile(filename): + raise PatoolError("`%s' is not a file" % filename) + + +def check_writable_filename(filename): + """Ensure that the given filename is writable.""" + if not os.access(filename, os.W_OK): + raise PatoolError("file `%s' is not writable" % filename) + + +def check_new_filename (filename): + """Check that filename does not already exist.""" + if os.path.exists(filename): + raise PatoolError("cannot overwrite existing file `%s'" % filename) + + +def check_archive_filelist (filenames): + """Check that file list is not empty and contains only existing files.""" + if not filenames: + raise PatoolError("cannot create archive with empty filelist") + for filename in filenames: + check_existing_filename(filename, onlyfiles=False) + + +def set_mode (filename, flags): + """Set mode flags for given filename if not already set.""" + try: + mode = os.lstat(filename).st_mode + except OSError: + # ignore + return + if not (mode & flags): + try: + os.chmod(filename, flags | mode) + except OSError as msg: + log_error("could not set mode flags for `%s': %s" % (filename, msg)) + + +def get_filesize(filename): + """Return file size in Bytes, or -1 on error.""" + return os.path.getsize(filename) + + +def strsize(b, grouping=True): + """Return human representation of bytes b. A negative number of bytes + raises a value error.""" + if b < 0: + raise ValueError("Invalid negative byte number") + if b < 1024: + return u"%sB" % locale.format("%d", b, grouping) + if b < 1024 * 10: + return u"%sKB" % locale.format("%d", (b // 1024), grouping) + if b < 1024 * 1024: + return u"%sKB" % locale.format("%.2f", (float(b) / 1024), grouping) + if b < 1024 * 1024 * 10: + return u"%sMB" % locale.format("%.2f", (float(b) / (1024*1024)), grouping) + if b < 1024 * 1024 * 1024: + return u"%sMB" % locale.format("%.1f", (float(b) / (1024*1024)), grouping) + if b < 1024 * 1024 * 1024 * 10: + return u"%sGB" % locale.format("%.2f", (float(b) / (1024*1024*1024)), grouping) + return u"%sGB" % locale.format("%.1f", (float(b) / (1024*1024*1024)), grouping) + + +def tmpdir (dir=None): + """Return a temporary directory for extraction.""" + return tempfile.mkdtemp(suffix='', prefix='Unpack_', dir=dir) + + +def tmpfile (dir=None, prefix="temp", suffix=None): + """Return a temporary file.""" + return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir)[1] + + +def shell_quote (value): + """Quote all shell metacharacters in given string value with strong + (ie. single) quotes, handling the single quote especially.""" + if os.name == 'nt': + return shell_quote_nt(value) + return "'%s'" % value.replace("'", r"'\''") + + +def shell_quote_nt (value): + """Quote argument for Windows system. Modeled after distutils + _nt_quote_args() function.""" + if " " in value: + return '"%s"' % value + return value + + +def stripext (filename): + """Return the basename without extension of given filename.""" + return os.path.splitext(os.path.basename(filename))[0] + + +def get_single_outfile (directory, archive, extension=""): + """Get output filename if archive is in a single file format like gzip.""" + outfile = os.path.join(directory, stripext(archive)) + if os.path.exists(outfile + extension): + # prevent overwriting existing files + i = 1 + newfile = "%s%d" % (outfile, i) + while os.path.exists(newfile + extension): + newfile = "%s%d" % (outfile, i) + i += 1 + outfile = newfile + return outfile + extension + + +def log_error (msg, out=sys.stderr): + """Print error message to stderr (or any other given output).""" + print("patool error:", msg, file=out) + + +def log_info (msg, out=sys.stdout): + """Print info message to stdout (or any other given output).""" + print("patool:", msg, file=out) + + +def log_internal_error(out=sys.stderr, etype=None, evalue=None, tb=None): + """Print internal error message (output defaults to stderr).""" + print(os.linesep, file=out) + print("""********** Oops, I did it again. ************* + +You have found an internal error in %(app)s. Please write a bug report +at %(url)s and include at least the information below: + +Not disclosing some of the information below due to privacy reasons is ok. +I will try to help you nonetheless, but you have to give me something +I can work with ;) . +""" % dict(app=configuration.AppName, url=configuration.SupportUrl), file=out) + if etype is None: + etype = sys.exc_info()[0] + if evalue is None: + evalue = sys.exc_info()[1] + print(etype, evalue, file=out) + if tb is None: + tb = sys.exc_info()[2] + traceback.print_exception(etype, evalue, tb, None, out) + print_app_info(out=out) + print_locale_info(out=out) + print(os.linesep, + "******** %s internal error, over and out ********" % configuration.AppName, file=out) + +def print_env_info(key, out=sys.stderr): + """If given environment key is defined, print it out.""" + value = os.getenv(key) + if value is not None: + print(key, "=", repr(value), file=out) + + +def print_locale_info(out=sys.stderr): + """Print locale info.""" + for key in ("LANGUAGE", "LC_ALL", "LC_CTYPE", "LANG"): + print_env_info(key, out=out) + + +def print_app_info(out=sys.stderr): + """Print system and application info (output defaults to stderr).""" + print("System info:", file=out) + print(configuration.App, file=out) + print("Python %(version)s on %(platform)s" % + {"version": sys.version, "platform": sys.platform}, file=out) + stime = strtime(time.time()) + print("Local time:", stime, file=out) + print("sys.argv", sys.argv, file=out) + + +def strtime(t): + """Return ISO 8601 formatted time.""" + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t)) + \ + strtimezone() + + +def strtimezone(): + """Return timezone info, %z on some platforms, but not supported on all. + """ + if time.daylight: + zone = time.altzone + else: + zone = time.timezone + return "%+04d" % (-zone//3600) + + +def p7zip_supports_rar(): + """Determine if the RAR codec is installed for 7z program.""" + if os.name == 'nt': + # Assume RAR support is compiled into the binary. + return True + # the subdirectory and codec name + codecname = 'p7zip/Codecs/Rar29.so' + # search canonical user library dirs + for libdir in ('/usr/lib', '/usr/local/lib', '/usr/lib64', '/usr/local/lib64', '/usr/lib/i386-linux-gnu', '/usr/lib/x86_64-linux-gnu'): + fname = os.path.join(libdir, codecname) + if os.path.exists(fname): + return True + return False + + +@memoized +def find_program (program): + """Look for program in environment PATH variable.""" + if os.name == 'nt': + # Add some well-known archiver programs to the search path + path = os.environ['PATH'] + path = append_to_path(path, get_nt_7z_dir()) + path = append_to_path(path, get_nt_mac_dir()) + path = append_to_path(path, get_nt_winrar_dir()) + else: + # use default path + path = None + return which(program, path=path) + + +def append_to_path (path, directory): + """Add a directory to the PATH environment variable, if it is a valid + directory.""" + if not os.path.isdir(directory) or directory in path: + return path + if not path.endswith(os.pathsep): + path += os.pathsep + return path + directory + + +def get_nt_7z_dir (): + """Return 7-Zip directory from registry, or an empty string.""" + # Python 3.x renamed the _winreg module to winreg + try: + import _winreg as winreg + except ImportError: + import winreg + try: + key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\7-Zip") + try: + return winreg.QueryValueEx(key, "Path")[0] + finally: + winreg.CloseKey(key) + except WindowsError: + return "" + + +def get_nt_program_dir (): + """Return the Windows program files directory.""" + progvar = "%ProgramFiles%" + return os.path.expandvars(progvar) + + +def get_nt_mac_dir (): + """Return Monkey Audio Compressor (MAC) directory.""" + return os.path.join(get_nt_program_dir(), "Monkey's Audio") + + +def get_nt_winrar_dir(): + """Return WinRAR directory.""" + return os.path.join(get_nt_program_dir(), "WinRAR") + + +def strlist_with_or (alist): + """Return comma separated string, and last entry appended with ' or '.""" + if len(alist) > 1: + return "%s or %s" % (", ".join(alist[:-1]), alist[-1]) + return ", ".join(alist) + + +def is_same_file (filename1, filename2): + """Check if filename1 and filename2 point to the same file object. + There can be false negatives, ie. the result is False, but it is + the same file anyway. Reason is that network filesystems can create + different paths to the same physical file. + """ + if filename1 == filename2: + return True + if os.name == 'posix': + return os.path.samefile(filename1, filename2) + return is_same_filename(filename1, filename2) + + +def is_same_filename (filename1, filename2): + """Check if filename1 and filename2 are the same filename.""" + return os.path.realpath(filename1) == os.path.realpath(filename2) + + +def link_or_copy(src, dst, verbosity=0): + """Try to make a hard link from src to dst and if that fails + copy the file. Hard links save some disk space and linking + should fail fast since no copying is involved. + """ + if verbosity > 0: + log_info("Copying %s -> %s" % (src, dst)) + try: + os.link(src, dst) + except (AttributeError, OSError): + try: + shutil.copy(src, dst) + except OSError as msg: + raise PatoolError(msg) + + +def chdir(directory): + """Remember and return current directory before calling os.chdir(). + If the current directory could not be determined, return None. + """ + try: + olddir = os.getcwd() + except OSError: + olddir = None + os.chdir(directory) + return olddir + + +init_mimedb()