Cleanup and Fixes and Inplace - Oh My

Add option to extract all archives to torrent root, destination folder, or true in-place.
Add sonarr/radarr support (marks torrent "incomplete" until extraction is done).
Bundle 7z.exe for windows users.
Fix GTK UI not working...at all.
Improve web and GTK UI so they're uniform, have radio buttons, etc.
This commit is contained in:
d8ahazard
2019-12-27 16:05:53 -06:00
parent a9140d33a1
commit 3d4740b95b
14 changed files with 537 additions and 438 deletions

View File

@@ -0,0 +1,265 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
# 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.
#
from __future__ import unicode_literals
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.configmanager import 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': '',
'extract_in_place': False,
'extract_selected_folder': False,
'extract_torrent_root': True,
'label_filter': ''}
if windows_check():
win_7z_exes = [
'7z.exe',
'C:\\Program Files\\7-Zip\\7z.exe',
'C:\\Program Files (x86)\\7-Zip\\7z.exe',
]
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(
'simpleextractor.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'])
tstatus = tid.get_status([], False, False, True)
do_extract = False
tid = component.get("TorrentManager").torrents[torrent_id]
tid_status = tid.get_status(["save_path", "name"])
tid.is_finished = False
log.info("Processing completed torrent %s", tstatus)
# Fetch our torrent's label
labels = self.get_labels(torrent_id)
log.info("Schmancy label collector: %s", labels)
# If we've set a label filter, process it
if self.config['label_filter'] is not "":
log.info("We should filter by label(s): %s", self.config['extract_labels'])
# Make sure there's actually a label
if len(labels) > 0:
for label in labels:
log.info("Label for torrent is %s", label)
# Check if it's more than one, split
if "," in self.config['extract_labels']:
log.info("And we have a list")
label_list = self.config['extract_labels'].split(",")
# And loop
for label in label_list:
if label.strip() == label:
log.info("This matches")
do_extract = True
break
# Otherwise, just check the whole string
else:
log.info("Single label string detected.")
if self.config['extract_labels'].strip() == label:
log.info("This matches")
do_extract = True
# We don't need to do this, but it adds sanity
else:
log.info("We have a label filter and no label, doing nothing")
do_extract = False
# Otherwise, we just extract everything
else:
log.info("No label, extracting.")
do_extract = True
# Now, extract if filter match or no filter set
extract_in_place = self.config["extract_in_place"]
extract_torrent_root = self.config["extract_torrent_root"]
if do_extract:
files = tid.get_files()
for f in files:
log.info("Handling file %s", f['path'])
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.info('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.info('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'])
)
# Get the destination path
dest = os.path.normpath(self.config["extract_path"])
name_dest = os.path.join(dest, tid_status["name"])
# Override destination if in_place_extraction is set
if extract_torrent_root:
dest = tid_status["save_path"]
name_dest = os.path.join(dest, tid_status["name"])
if extract_in_place and ((not os.path.exists(name_dest)) or os.path.isdir(name_dest)):
dest = name_dest
try:
os.makedirs(dest)
except OSError as ex:
if not (ex.errno == errno.EEXIST and os.path.isdir(dest)):
log.error("EXTRACTOR: 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.info(
'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)
tid.is_finished = True
def get_labels(self, torrent_id):
"""
Asking the system about the labels isn't very cool, so try this instead
"""
labels = []
label_config = ConfigManager('label.conf', defaults=False)
if label_config is not False:
log.info("We have a Label config")
if 'torrent_labels' in label_config:
if torrent_id in label_config['torrent_labels']:
log.info("Data from Label plugin: %s", label_config['torrent_labels'][torrent_id])
labels.append(label_config['torrent_labels'][torrent_id])
label_plus_config = ConfigManager('labelplus.conf', defaults=False)
if label_plus_config is not False:
log.info("We have a label plus config")
if 'mappings' in label_plus_config:
if torrent_id in label_plus_config['mappings']:
mapping = label_plus_config['mappings'][torrent_id]
log.info("We have a label plus mapping: %s", mapping)
labels.append(label_plus_config['labels'][mapping]['name'])
return labels
@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