diff --git a/.gitignore b/.gitignore index a45b2cf..74fabc5 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ docs/_build/ # PyBuilder target/ +/venv* diff --git a/deluge_simpleextractor/7z.dll b/deluge_simpleextractor/7z.dll new file mode 100644 index 0000000..b32d7bf Binary files /dev/null and b/deluge_simpleextractor/7z.dll differ diff --git a/deluge_simpleextractor/7z.exe b/deluge_simpleextractor/7z.exe new file mode 100644 index 0000000..37b8514 Binary files /dev/null and b/deluge_simpleextractor/7z.exe differ diff --git a/simpleextractor/__init__.py b/deluge_simpleextractor/__init__.py similarity index 100% rename from simpleextractor/__init__.py rename to deluge_simpleextractor/__init__.py diff --git a/simpleextractor/common.py b/deluge_simpleextractor/common.py similarity index 89% rename from simpleextractor/common.py rename to deluge_simpleextractor/common.py index 2435ab6..4c9db09 100644 --- a/simpleextractor/common.py +++ b/deluge_simpleextractor/common.py @@ -20,4 +20,4 @@ from pkg_resources import resource_filename def get_resource(filename): - return resource_filename("simpleextractor", os.path.join('data', filename)) + return resource_filename(__package__, os.path.join('data', filename)) diff --git a/simpleextractor/core.py b/deluge_simpleextractor/core.py similarity index 86% rename from simpleextractor/core.py rename to deluge_simpleextractor/core.py index 3a377d0..fa9fb43 100644 --- a/simpleextractor/core.py +++ b/deluge_simpleextractor/core.py @@ -29,7 +29,11 @@ from deluge.plugins.pluginbase import CorePluginBase log = logging.getLogger(__name__) -DEFAULT_PREFS = {'extract_path': '', 'use_name_folder': False, 'in_place_extraction': True, 'extract_labels': ''} +DEFAULT_PREFS = {'extract_path': '', + 'extract_in_place': False, + 'extract_selected_folder': False, + 'extract_torrent_root': True, + 'label_filter': ''} if windows_check(): win_7z_exes = [ @@ -38,20 +42,6 @@ if windows_check(): 'C:\\Program Files (x86)\\7-Zip\\7z.exe', ] - try: - import winreg - except ImportError: - import _winreg as winreg # For Python 2. - - try: - hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\7-Zip') - except WindowsError: - 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. @@ -128,12 +118,16 @@ class Core(CorePluginBase): 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['extract_labels'] is not "": + 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: @@ -165,6 +159,9 @@ class Core(CorePluginBase): 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: @@ -183,28 +180,29 @@ class Core(CorePluginBase): 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']) + + # 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 self.config["in_place_extraction"]: - name = tid_status["name"] - save_path = tid_status["download_location"] - dest = os.path.join(save_path, name) - log.info("Save path is %s, dest is %s, fpath is %s", save_path, dest, fpath) + if extract_torrent_root: + dest = tid_status["save_path"] + name_dest = os.path.join(dest, tid_status["name"]) - # Create the destination folder if it doesn't exist - if not os.path.exists(dest): - 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 + 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. @@ -228,6 +226,7 @@ class Core(CorePluginBase): 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): """ diff --git a/deluge_simpleextractor/data/simpleextractor.js b/deluge_simpleextractor/data/simpleextractor.js new file mode 100644 index 0000000..5a32a9f --- /dev/null +++ b/deluge_simpleextractor/data/simpleextractor.js @@ -0,0 +1,178 @@ +/*! + * simpleextractor.js + * + * Copyright (c) Damien Churchill 2010 + * Copyright (C) Digitalhigh 2019 + * + * + */ + +Ext.ns('Deluge.ux.preferences'); + +/** + * @class Deluge.ux.preferences.SimpleExtractorPage + * @extends Ext.Panel + */ +Deluge.ux.preferences.SimpleExtractorPage = Ext.extend(Ext.Panel, { + + title: _('SimpleExtractor'), + layout: 'fit', + border: false, + + initComponent: function() { + Deluge.ux.preferences.SimpleExtractorPage.superclass.initComponent.call(this); + + this.form = this.add({ + xtype: 'form', + layout: 'form', + border: false, + autoHeight: true + }); + + + this.behaviorSet = this.form.add({ + xtype: 'fieldset', + border: false, + title: '', + autoHeight: true, + labelAlign: 'top', + labelWidth: 80, + defaultType: 'textfield' + }); + + this.behaviorSet.add({ + xtype: 'label', + fieldLabel: _('Extract Behavior:
'), + labelSeparator : '', + name: '', + width: '97%' + }); + + // Behavior Label + + // Add radio group for extract behavior + this.extractBehavior = this.behaviorSet.add({ + xtype: 'radiogroup', + columns: 1, + colspan: 2, + style: 'margin-left: 10px', + items: [ + { + boxLabel: _('Selected Folder'), + name: 'extract_behavior', + inputValue: "extract_selected_folder" + }, + { + boxLabel: _('Torrent Root'), + name: 'extract_behavior', + inputValue: "extract_torrent_root" + }, + { + boxLabel: _('In-Place'), + name: 'extract_behavior', + inputValue: "extract_in_place" + } + ], + }); + + this.destinationSet = this.form.add({ + xtype: 'fieldset', + border: false, + title: '', + autoHeight: true, + labelAlign: 'top', + labelWidth: 80, + defaultType: 'textfield' + }); + + // Destination label + this.extractPath = this.destinationSet.add({ + fieldLabel: _('Destination:
'), + name: 'extract_path', + labelSeparator : '', + width: '97%' + }); + + + this.labelSet = this.form.add({ + xtype: 'fieldset', + border: false, + title: '', + autoHeight: true, + labelAlign: 'top', + labelWidth: 80, + defaultType: 'textfield' + }); + + // Label Filter Label + this.labelFilter = this.labelSet.add({ + fieldLabel: _('Label Filtering:
'), + name: 'label_filter', + labelSeparator : '', + width: '97%' + }); + + this.labelSet.add({ + xtype: 'label', + fieldLabel: _('
Comma-separated, leave blank for none.'), + labelSeparator : '', + name: '', + width: '97%' + }); + + + + + + this.on('show', this.updateConfig, this); + }, + + onApply: function() { + // build settings object + var config = {}; + config['extract_path'] = this.extractPath.getValue(); + var eBehavior = this.extractBehavior.getValue(); + config['extract_in_place'] = false; + config['extract_torrent_root'] = false; + config['extract_selected_folder'] = false; + config[eBehavior] = true; + config['label_filter'] = this.labelFilter.getValue(); + + deluge.client.simpleextractor.set_config(config); + }, + + onOk: function() { + this.onApply(); + }, + + updateConfig: function() { + deluge.client.simpleextractor.get_config({ + success: function(config) { + this.extractPath.setValue(config['extract_path']); + var behavior = "extract_selected_folder"; + if (config['extract_in_place']) { + behavior = 'extract_in_place'; + } + if (config['extract_torrent_root']) { + behavior = 'extract_torrent_root'; + } + this.extractBehavior.setValue(behavior); + this.labelFilter.setValue(config['label_filter']); + }, + scope: this + }); + } +}); + + +Deluge.plugins.SimpleExtractorPlugin = Ext.extend(Deluge.Plugin, { + name: 'SimpleExtractor', + onDisable: function() { + deluge.preferences.removePage(this.prefsPage); + }, + + onEnable: function() { + this.prefsPage = deluge.preferences.addPage(new Deluge.ux.preferences.SimpleExtractorPage()); + } +}); +Deluge.registerPlugin('SimpleExtractor', Deluge.plugins.SimpleExtractorPlugin); diff --git a/deluge_simpleextractor/data/simpleextractor_prefs.ui b/deluge_simpleextractor/data/simpleextractor_prefs.ui new file mode 100644 index 0000000..f4d3c04 --- /dev/null +++ b/deluge_simpleextractor/data/simpleextractor_prefs.ui @@ -0,0 +1,221 @@ + + + + + False + + + True + False + vertical + 5 + + + True + False + 0 + 0 + none + + + True + False + start + start + 5 + vertical + 5 + + + True + False + vertical + + + Selected Folder + True + True + False + All torrents will be extracted to the selected folder below. + 0 + True + True + extract_torrent_root + + + False + True + 1 + + + + + Torrent Root + True + True + False + Any found archives will be extracted to the root of the torrent directory. + 0 + True + True + + + False + True + 2 + + + + + In-Place + True + True + False + Found archives will be extracted where they are found. + 0 + True + True + extract_torrent_root + + + False + True + 3 + + + + + False + True + 1 + + + + + + + True + False + <b>Extract Behavior:</b> + True + + + + + False + True + 0 + + + + + True + False + 0 + none + + + True + False + 5 + + + False + select-folder + Select A Folder + + + True + True + 0 + + + + + True + False + False + + + True + True + 1 + + + + + + + True + False + <b>Destination:</b> + + True + + + + + False + True + 1 + + + + + True + False + 0 + none + + + True + False + vertical + 5 + + + True + True + Enter one or more labels to extract, separated by commas. Leave to extract any archive found. + + + False + False + 0 + + + + + True + False + Enter labels to extract (comma-separated, leave blank for all) + True + + + True + True + 1 + + + + + + + True + False + <b>Label Filtering:</b> + + True + + + + + False + True + 2 + + + + + + diff --git a/deluge_simpleextractor/gtkui.py b/deluge_simpleextractor/gtkui.py new file mode 100644 index 0000000..be85f56 --- /dev/null +++ b/deluge_simpleextractor/gtkui.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009 Andrew Resch +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2019-2020 Digitalhigh +# +# + +from __future__ import unicode_literals + +import logging + +import gi # isort:skip (Required before Gtk import). + +gi.require_version('Gtk', '3.0') # NOQA: E402 + +# isort:imports-thirdparty +from gi.repository import Gtk + +# isort:imports-firstparty +import deluge.component as component +from deluge.plugins.pluginbase import Gtk3PluginBase +from deluge.ui.client import client + +# isort:imports-localfolder +from .common import get_resource + +log = logging.getLogger(__name__) + + +class GtkUI(Gtk3PluginBase): + def enable(self): + self.plugin = component.get('PluginManager') + self.builder = Gtk.Builder() + self.builder.add_from_file(get_resource('simpleextractor_prefs.ui')) + + component.get('Preferences').add_page( + _('Simple Extractor'), self.builder.get_object('extractor_prefs_box') + ) + + self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs) + self.plugin.register_hook('on_show_prefs', self.on_show_prefs) + self.on_show_prefs() + + def disable(self): + component.get('Preferences').remove_page(_('Simple Extractor')) + self.plugin.deregister_hook( + 'on_apply_prefs', self.on_apply_prefs + ) + self.plugin.deregister_hook( + 'on_show_prefs', self.on_show_prefs + ) + del self.builder + + def on_apply_prefs(self): + log.debug('applying prefs for Simple Extractor') + if client.is_localhost(): + path = self.builder.get_object('folderchooser_path').get_filename() + else: + path = self.builder.get_object('extract_path').get_text() + + config = { + 'extract_path': path, + 'extract_selected_folder': self.builder.get_object("extract_selected_folder").get_active(), + 'extract_in_place': self.builder.get_object("extract_in_place").get_active(), + 'extract_torrent_root': self.builder.get_object("extract_torrent_root").get_active(), + 'label_filter': self.builder.get_object("label_filter").get_text() + } + + client.simpleextractor.set_config(config) + + def on_show_prefs(self): + def on_get_config(config): + if client.is_localhost(): + self.builder.get_object('folderchooser_path').set_current_folder(config['extract_path']) + self.builder.get_object('folderchooser_path').show() + self.builder.get_object('extract_path').hide() + else: + self.builder.get_object('extract_path').set_text(config['extract_path']) + self.builder.get_object('folderchooser_path').hide() + self.builder.get_object('extract_path').show() + + self.builder.get_object('extract_selected_folder').set_active( + config['extract_selected_folder'] + ) + self.builder.get_object('extract_torrent_root').set_active( + config['extract_torrent_root'] + ) + self.builder.get_object('extract_in_place').set_active( + config['extract_in_place'] + ) + self.builder.get_object('label_filter').set_text(config['label_filter']) + + client.simpleextractor.get_config().addCallback(on_get_config) diff --git a/simpleextractor/webui.py b/deluge_simpleextractor/webui.py similarity index 100% rename from simpleextractor/webui.py rename to deluge_simpleextractor/webui.py diff --git a/setup.py b/setup.py index 0ab4ae7..ccb537a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ from setuptools import find_packages, setup __plugin_name__ = 'SimpleExtractor' __author__ = 'Digitalhigh' __author_email__ = 'donate.to.digitalhigh@gmail.com' -__version__ = '1.0' +__version__ = '1.1' __url__ = 'github.com/d8ahazard/deluge-extractor' __license__ = 'GPLv3' __description__ = 'Extract files upon torrent completion' @@ -30,7 +30,7 @@ Windows support: .rar, .zip, .tar, .7z, .xz, .lzma Note: Will not extract with 'Move Completed' enabled """ -__pkg_data__ = {__plugin_name__.lower(): ['template/*', 'data/*']} +__pkg_data__ = {'deluge_' + __plugin_name__.lower(): ['data/*', '7z.dll', '7z.exe']} setup( name=__plugin_name__, @@ -40,16 +40,17 @@ setup( author_email=__author_email__, url=__url__, license=__license__, - long_description=__long_description__ if __long_description__ else __description__, - packages=[__plugin_name__.lower()], + zip_safe=False, + long_description=__long_description__, + packages=find_packages(), package_data=__pkg_data__, entry_points=""" [deluge.plugin.core] - %s = %s:CorePlugin + %s = deluge_%s:CorePlugin [deluge.plugin.gtk3ui] - %s = %s:GtkUIPlugin + %s = deluge_%s:GtkUIPlugin [deluge.plugin.web] - %s = %s:WebUIPlugin + %s = deluge_%s:WebUIPlugin """ % ((__plugin_name__, __plugin_name__.lower()) * 3), ) diff --git a/simpleextractor/data/simpleextractor.js b/simpleextractor/data/simpleextractor.js deleted file mode 100644 index 471b65a..0000000 --- a/simpleextractor/data/simpleextractor.js +++ /dev/null @@ -1,131 +0,0 @@ -/*! - * simpleextractor.js - * - * Copyright (c) Damien Churchill 2010 - * Copyright (C) Digitalhigh 2019 - * - * 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. - * - */ - -Ext.ns('Deluge.ux.preferences'); - -/** - * @class Deluge.ux.preferences.SimpleExtractorPage - * @extends Ext.Panel - */ -Deluge.ux.preferences.SimpleExtractorPage = Ext.extend(Ext.Panel, { - - title: _('SimpleExtractor'), - layout: 'fit', - border: false, - - initComponent: function() { - Deluge.ux.preferences.SimpleExtractorPage.superclass.initComponent.call(this); - - this.form = this.add({ - xtype: 'form', - layout: 'form', - border: false, - autoHeight: true - }); - - fieldset = this.form.add({ - xtype: 'fieldset', - border: false, - title: '', - autoHeight: true, - labelAlign: 'top', - labelWidth: 80, - defaultType: 'textfield' - }); - - this.extract_path = fieldset.add({ - fieldLabel: _('Extract to:'), - labelSeparator : '', - name: 'extract_path', - width: '97%' - }); - - - this.use_name_folder = fieldset.add({ - xtype: 'checkbox', - name: 'use_name_folder', - height: 22, - hideLabel: true, - boxLabel: _('Create torrent name sub-folder') - }); - - this.in_place_extraction = fieldset.add({ - xtype: 'checkbox', - name: 'in_place_extraction', - height: 22, - hideLabel: true, - boxLabel: _('Extract torrent in-place') - }); - - fieldset2 = this.form.add({ - xtype: 'fieldset', - border: false, - title: '', - autoHeight: true, - labelAlign: 'top', - labelWidth: 80, - defaultType: 'textfield' - }); - - this.extract_labels = fieldset.add({ - fieldLabel: _('Label Filter:'), - labelSeparator : '', - name: 'extract_labels', - width: '97%' - }); - - this.on('show', this.updateConfig, this); - }, - - onApply: function() { - // build settings object - var config = { } - - config['extract_path'] = this.extract_path.getValue(); - config['use_name_folder'] = this.use_name_folder.getValue(); - config['in_place_extraction'] = this.in_place_extraction.getValue(); - config['extract_labels'] = this.extract_labels.getValue(); - - deluge.client.simpleextractor.set_config(config); - }, - - onOk: function() { - this.onApply(); - }, - - updateConfig: function() { - deluge.client.simpleextractor.get_config({ - success: function(config) { - this.extract_path.setValue(config['extract_path']); - this.use_name_folder.setValue(config['use_name_folder']); - this.in_place_extraction.setValue(config['in_place_extraction']); - this.extract_labels.setValue(config['extract_labels']); - }, - scope: this - }); - } -}); - - -Deluge.plugins.SimpleExtractorPlugin = Ext.extend(Deluge.Plugin, { - - name: 'SimpleExtractor', - - onDisable: function() { - deluge.preferences.removePage(this.prefsPage); - }, - - onEnable: function() { - this.prefsPage = deluge.preferences.addPage(new Deluge.ux.preferences.SimpleExtractorPage()); - } -}); -Deluge.registerPlugin('SimpleExtractor', Deluge.plugins.SimpleExtractorPlugin); diff --git a/simpleextractor/data/simpleextractor_prefs.ui b/simpleextractor/data/simpleextractor_prefs.ui deleted file mode 100644 index 4cf1bf7..0000000 --- a/simpleextractor/data/simpleextractor_prefs.ui +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - False - - - - - - True - False - 5 - vertical - - - True - False - 0 - none - - - True - False - 5 - 5 - vertical - - - True - False - 5 - - - True - False - Extract to: - - - False - False - 0 - - - - - True - False - - - False - select-folder - Select A Folder - - - True - True - 0 - - - - - True - False - False - - - True - True - 1 - - - - - True - True - 1 - - - - - False - False - 0 - - - - - Create torrent name sub-folder - True - True - False - This option will create a sub-folder using the torrent's name within the selected extract folder and put the extracted files there. - True - - - False - False - 1 - - - - - True - False - 5 - - - True - False - Enter labels to extract (comma-separated, leave blank for all) - True - - - True - True - 0 - - - - - True - True - False - Enter one or more labels to extract, separated by commas. Leave to extract any archive found. - True - - - False - False - 1 - - - - - True - True - 0 - - - - - True - True - 2 - - - - - True - False - <b>General</b> - True - - - - - True - True - 0 - - - - - - diff --git a/simpleextractor/gtkui.py b/simpleextractor/gtkui.py deleted file mode 100644 index a47f0c6..0000000 --- a/simpleextractor/gtkui.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -# -# 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. -# - -from __future__ import unicode_literals - -import logging - -import gi # isort:skip (Required before Gtk import). - -gi.require_version('Gtk', '3.0') # NOQA: E402 - -# isort:imports-thirdparty -from gi.repository import Gtk - -# isort:imports-firstparty -import deluge.component as component -from deluge.plugins.pluginbase import Gtk3PluginBase -from deluge.ui.client import client - -# isort:imports-localfolder -from .common import get_resource - -log = logging.getLogger(__name__) - - -class GtkUI(Gtk3PluginBase): - def enable(self): - self.builder = Gtk.Builder() - self.builder.add_from_file(get_resource('extractor_prefs.ui')) - - component.get('Preferences').add_page( - _('SimpleExtractor'), self.builder.get_object('extractor_prefs_box') - ) - component.get('PluginManager').register_hook( - 'on_apply_prefs', self.on_apply_prefs - ) - component.get('PluginManager').register_hook( - 'on_show_prefs', self.on_show_prefs - ) - self.on_show_prefs() - - def disable(self): - component.get('Preferences').remove_page(_('SimpleExtractor')) - component.get('PluginManager').deregister_hook( - 'on_apply_prefs', self.on_apply_prefs - ) - component.get('PluginManager').deregister_hook( - 'on_show_prefs', self.on_show_prefs - ) - del self.builder - - def on_apply_prefs(self): - log.debug('applying prefs for Simple Extractor') - if client.is_localhost(): - path = self.builder.get_object('folderchooser_path').get_filename() - else: - path = self.builder.get_object('entry_path').get_text() - - config = { - 'extract_path': path, - 'use_name_folder': self.builder.get_object('chk_use_name').get_active(), - 'in_place_extraction': self.builder.get_object("chk_in_place_extraction").get_active(), - 'extract_labels': self.builder.get_object("entry_labels").get_text() - } - - client.extractor.set_config(config) - - def on_show_prefs(self): - if client.is_localhost(): - self.builder.get_object('folderchooser_path').show() - self.builder.get_object('entry_path').hide() - else: - self.builder.get_object('folderchooser_path').hide() - self.builder.get_object('entry_path').show() - - def on_get_config(config): - if client.is_localhost(): - self.builder.get_object('folderchooser_path').set_current_folder( - config['extract_path'] - ) - else: - self.builder.get_object('entry_path').set_text(config['extract_path']) - - self.builder.get_object('chk_use_name').set_active( - config['use_name_folder'] - ) - self.builder.get_object('chk_in_place_extraction').set_active(config['in_place_extraction']) - self.builder.get_object('entry_labels').set_text(config['extract_labels']) - - client.extractor.get_config().addCallback(on_get_config)