# # Copyright (C) 2009 Andrew Resch # # Basic plugin template created by: # Copyright (C) 2008 Martijn Voncken # Copyright (C) 2007-2009 Andrew Resch # # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with # the additional special exception to link portions of this program with the OpenSSL library. # See LICENSE for more details. # import errno import logging import os from twisted.internet.utils import getProcessOutputAndValue from twisted.python.procutils import which import deluge.component as component import deluge.configmanager from deluge.common import windows_check from deluge.core.rpcserver import export from deluge.plugins.pluginbase import CorePluginBase log = logging.getLogger(__name__) DEFAULT_PREFS = {'extract_path': '', 'use_name_folder': True} if windows_check(): win_7z_exes = [ '7z.exe', 'C:\\Program Files\\7-Zip\\7z.exe', 'C:\\Program Files (x86)\\7-Zip\\7z.exe', ] import winreg try: hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\7-Zip') except OSError: pass else: win_7z_path = os.path.join(winreg.QueryValueEx(hkey, 'Path')[0], '7z.exe') winreg.CloseKey(hkey) win_7z_exes.insert(1, win_7z_path) switch_7z = 'x -y' # Future suport: # 7-zip cannot extract tar.* with single command. # ".tar.gz", ".tgz", # ".tar.bz2", ".tbz", # ".tar.lzma", ".tlz", # ".tar.xz", ".txz", exts_7z = ['.rar', '.zip', '.tar', '.7z', '.xz', '.lzma'] for win_7z_exe in win_7z_exes: if which(win_7z_exe): EXTRACT_COMMANDS = dict.fromkeys(exts_7z, [win_7z_exe, switch_7z]) break else: required_cmds = ['unrar', 'unzip', 'tar', 'unxz', 'unlzma', '7zr', 'bunzip2'] # Possible future suport: # gunzip: gz (cmd will delete original archive) # the following do not extract to dest dir # ".xz": ["xz", "-d --keep"], # ".lzma": ["xz", "-d --format=lzma --keep"], # ".bz2": ["bzip2", "-d --keep"], EXTRACT_COMMANDS = { '.rar': ['unrar', 'x -o+ -y'], '.tar': ['tar', '-xf'], '.zip': ['unzip', ''], '.tar.gz': ['tar', '-xzf'], '.tgz': ['tar', '-xzf'], '.tar.bz2': ['tar', '-xjf'], '.tbz': ['tar', '-xjf'], '.tar.lzma': ['tar', '--lzma -xf'], '.tlz': ['tar', '--lzma -xf'], '.tar.xz': ['tar', '--xz -xf'], '.txz': ['tar', '--xz -xf'], '.7z': ['7zr', 'x'], } # Test command exists and if not, remove. for command in required_cmds: if not which(command): for k, v in list(EXTRACT_COMMANDS.items()): if command in v[0]: log.warning('%s not found, disabling support for %s', command, k) del EXTRACT_COMMANDS[k] if not EXTRACT_COMMANDS: raise Exception('No archive extracting programs found, plugin will be disabled') class Core(CorePluginBase): def enable(self): self.config = deluge.configmanager.ConfigManager( 'extractor.conf', DEFAULT_PREFS ) if not self.config['extract_path']: self.config['extract_path'] = deluge.configmanager.ConfigManager( 'core.conf' )['download_location'] component.get('EventManager').register_event_handler( 'TorrentFinishedEvent', self._on_torrent_finished ) def disable(self): component.get('EventManager').deregister_event_handler( 'TorrentFinishedEvent', self._on_torrent_finished ) def update(self): pass def _on_torrent_finished(self, torrent_id): """ This is called when a torrent finishes and checks if any files to extract. """ tid = component.get('TorrentManager').torrents[torrent_id] tid_status = tid.get_status(['download_location', 'name']) files = tid.get_files() for f in files: file_root, file_ext = os.path.splitext(f['path']) file_ext_sec = os.path.splitext(file_root)[1] if file_ext_sec and file_ext_sec + file_ext in EXTRACT_COMMANDS: file_ext = file_ext_sec + file_ext elif file_ext not in EXTRACT_COMMANDS or file_ext_sec == '.tar': log.debug('Cannot extract file with unknown file type: %s', f['path']) continue elif file_ext == '.rar' and 'part' in file_ext_sec: part_num = file_ext_sec.split('part')[1] if part_num.isdigit() and int(part_num) != 1: log.debug('Skipping remaining multi-part rar files: %s', f['path']) continue cmd = EXTRACT_COMMANDS[file_ext] fpath = os.path.join( tid_status['download_location'], os.path.normpath(f['path']) ) dest = os.path.normpath(self.config['extract_path']) if self.config['use_name_folder']: dest = os.path.join(dest, tid_status['name']) try: os.makedirs(dest) except OSError as ex: if not (ex.errno == errno.EEXIST and os.path.isdir(dest)): log.error('Error creating destination folder: %s', ex) break def on_extract(result, torrent_id, fpath): # Check command exit code. if not result[2]: log.info('Extract successful: %s (%s)', fpath, torrent_id) else: log.error( 'Extract failed: %s (%s) %s', fpath, torrent_id, result[1] ) # Run the command and add callback. log.debug( 'Extracting %s from %s with %s %s to %s', fpath, torrent_id, cmd[0], cmd[1], dest, ) d = getProcessOutputAndValue( cmd[0], cmd[1].split() + [str(fpath)], os.environ, str(dest) ) d.addCallback(on_extract, torrent_id, fpath) @export def set_config(self, config): """Sets the config dictionary.""" for key in config: self.config[key] = config[key] self.config.save() @export def get_config(self): """Returns the config dictionary.""" return self.config.config