diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a15e44f --- /dev/null +++ b/setup.py @@ -0,0 +1,83 @@ +# +# setup.py +# +# Copyright (C) 2009 Andrew Resch +# Copyright (C) 2015 Chris Yereaztian +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# Deluge is free software. +# +# You may 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. +# +# deluge 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 deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +# + +from setuptools import setup + +__plugin_name__ = "SimpleExtractor" +__author__ = "Chris Yereaztian" +__author_email__ = "chris.yereaztian@gmail.com" +__version__ = "0.4.1" +__url__ = "github.com/cvarta/deluge-extractor" +__license__ = "GPLv3" +__description__ = "Extract files upon torrent completion" +__long_description__ = """ +Extract files upon torrent completion + +Supports: .rar, .tar, .zip, .7z .tar.gz, .tgz, .tar.bz2, .tbz .tar.lzma, .tlz, .tar.xz, .txz + +Windows support: .rar, .zip, .tar, .7z, .xz, .lzma +( Requires 7-zip installed: http://www.7-zip.org/ ) + +Note: Will not extract with 'Move Completed' enabled +""" +__pkg_data__ = {__plugin_name__.lower(): ["template/*", "data/*"]} + +setup( + name=__plugin_name__, + version=__version__, + description=__description__, + author=__author__, + author_email=__author_email__, + url=__url__, + license=__license__, + long_description=__long_description__ if __long_description__ else __description__, + + packages=[__plugin_name__.lower()], + package_data = __pkg_data__, + + entry_points=""" + [deluge.plugin.core] + %s = %s:CorePlugin + [deluge.plugin.gtkui] + %s = %s:GtkUIPlugin + [deluge.plugin.web] + %s = %s:WebUIPlugin + """ % ((__plugin_name__, __plugin_name__.lower())*3) +) diff --git a/simpleextractor/__init__.py b/simpleextractor/__init__.py new file mode 100644 index 0000000..c78100f --- /dev/null +++ b/simpleextractor/__init__.py @@ -0,0 +1,59 @@ +# +# __init__.py +# +# Copyright (C) 2009 Andrew Resch +# Copyright (C) 2015 Chris Yereaztian +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# Deluge is free software. +# +# You may 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. +# +# deluge 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 deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +# + +from deluge.plugins.init import PluginInitBase + +class CorePlugin(PluginInitBase): + def __init__(self, plugin_name): + from core import Core as _plugin_cls + self._plugin_cls = _plugin_cls + super(CorePlugin, self).__init__(plugin_name) + +class GtkUIPlugin(PluginInitBase): + def __init__(self, plugin_name): + from gtkui import GtkUI as _plugin_cls + self._plugin_cls = _plugin_cls + super(GtkUIPlugin, self).__init__(plugin_name) + +class WebUIPlugin(PluginInitBase): + def __init__(self, plugin_name): + from webui import WebUI as _plugin_cls + self._plugin_cls = _plugin_cls + super(WebUIPlugin, self).__init__(plugin_name) diff --git a/simpleextractor/common.py b/simpleextractor/common.py new file mode 100644 index 0000000..950c719 --- /dev/null +++ b/simpleextractor/common.py @@ -0,0 +1,39 @@ +# +# common.py +# +# Copyright (C) 2009 Andrew Resch +# Copyright (C) 2015 Chris Yereaztian +# +# Deluge is free software. +# +# You may 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. +# +# deluge 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 deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +# + +def get_resource(filename): + import pkg_resources, os + return pkg_resources.resource_filename("simpleextractor", os.path.join("data", filename)) diff --git a/simpleextractor/core.py b/simpleextractor/core.py new file mode 100644 index 0000000..2d57f5d --- /dev/null +++ b/simpleextractor/core.py @@ -0,0 +1,198 @@ +# +# core.py +# +# Copyright (C) 2009 Andrew Resch +# Copyright (C) 2015 Chris Yereaztian +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# Deluge is free software. +# +# You may 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. +# +# deluge 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 deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +# + +import os + +from twisted.internet.utils import getProcessValue + +from deluge.log import LOG as log +from deluge.plugins.pluginbase import CorePluginBase +import deluge.component as component +import deluge.configmanager +from deluge.core.rpcserver import export +from deluge.common import windows_check +from extractor.which import which + +DEFAULT_PREFS = { + "extract_path": "", + "use_name_folder": False, + "in_place_extraction": 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 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. + # ".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 cmd in required_cmds: + if not which(cmd): + for k,v in EXTRACT_COMMANDS.items(): + if cmd in v[0]: + log.warning("EXTRACTOR: %s not found, disabling support for %s", cmd, k) + del EXTRACT_COMMANDS[k] + +if not EXTRACT_COMMANDS: + raise Exception("EXTRACTOR: 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(["save_path", "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.warning("EXTRACTOR: Can't extract file with unknown file type: %s" % f["path"]) + continue + cmd = EXTRACT_COMMANDS[file_ext] + + # Now that we have the cmd, lets run it to extract the files + fpath = os.path.join(tid_status["save_path"], os.path.normpath(f["path"])) + + # Get the destination path + dest = os.path.normpath(self.config["extract_path"]) + if self.config["use_name_folder"]: + name = tid_status["name"] + dest = os.path.join(dest, name) + + # Override destination if in_place_extraction is set + if self.config["in_place_extraction"]: + name = tid_status["name"] + save_path = tid_status["save_path"] + dest = os.path.join(save_path,name) + + # Create the destination folder if it doesn't exist + if not os.path.exists(dest): + try: + os.makedirs(dest) + except Exception, e: + log.error("EXTRACTOR: Error creating destination folder: %s", e) + return + + def on_extract_success(result, torrent_id, fpath): + # XXX: Emit an event + log.info("EXTRACTOR: Extract successful: %s (%s)", fpath, torrent_id) + + def on_extract_failed(result, torrent_id, fpath): + # XXX: Emit an event + log.error("EXTRACTOR: Extract failed: %s (%s)", fpath, torrent_id) + + # Run the command and add some callbacks + log.debug("EXTRACTOR: Extracting %s with %s %s to %s", fpath, cmd[0], cmd[1], dest) + d = getProcessValue(cmd[0], cmd[1].split() + [str(fpath)], {}, str(dest)) + d.addCallback(on_extract_success, torrent_id, fpath) + d.addErrback(on_extract_failed, torrent_id, fpath) + + @export + def set_config(self, config): + "sets the config dictionary" + for key in config.keys(): + self.config[key] = config[key] + self.config.save() + + @export + def get_config(self): + "returns the config dictionary" + return self.config.config diff --git a/simpleextractor/data/simpleextractor.js b/simpleextractor/data/simpleextractor.js new file mode 100644 index 0000000..a43b0b5 --- /dev/null +++ b/simpleextractor/data/simpleextractor.js @@ -0,0 +1,112 @@ +/*! + * simpleextractor.js + * + * Copyright (c) Damien Churchill 2010 + * Copyright (C) 2015 Chris Yereaztian + * + * 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') + }); + + 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(); + + 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']); + }, + 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.glade b/simpleextractor/data/simpleextractor_prefs.glade new file mode 100644 index 0000000..2dfc7e9 --- /dev/null +++ b/simpleextractor/data/simpleextractor_prefs.glade @@ -0,0 +1,117 @@ + + + + + + + + True + 5 + + + True + 0 + none + + + True + 5 + 5 + + + True + 5 + + + True + Extract to: + + + False + False + 0 + + + + + True + + + select-folder + Select A Folder + + + 0 + + + + + True + + + 1 + + + + + 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 + + + + + Extract torrent in place + True + True + False + This option will extract the torrent in place. + True + + + False + False + 1 + + + + + + + True + <b>General</b> + True + + + label_item + + + + + 0 + + + + + + diff --git a/simpleextractor/gtkui.py b/simpleextractor/gtkui.py new file mode 100644 index 0000000..dac45ec --- /dev/null +++ b/simpleextractor/gtkui.py @@ -0,0 +1,98 @@ +# +# gtkui.py +# +# Copyright (C) 2009 Andrew Resch +# Copyright (C) 2015 Chris Yereaztian +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# Deluge is free software. +# +# You may 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. +# +# deluge 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 deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +# + +import gtk + +from deluge.log import LOG as log +from deluge.ui.client import client +from deluge.plugins.pluginbase import GtkPluginBase +import deluge.component as component +import deluge.common + +from common import get_resource + +class GtkUI(GtkPluginBase): + def enable(self): + self.glade = gtk.glade.XML(get_resource("simpleextractor_prefs.glade")) + + component.get("Preferences").add_page(_("SimpleExtractor"), self.glade.get_widget("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(_("Extractor")) + 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.glade + + def on_apply_prefs(self): + log.debug("applying prefs for Extractor") + if client.is_localhost(): + path = self.glade.get_widget("folderchooser_path").get_filename() + else: + path = self.glade.get_widget("entry_path").get_text() + + config = { + "extract_path": path, + "use_name_folder": self.glade.get_widget("chk_use_name").get_active(), + "in_place_extraction": self.glade.get_widget("chk_in_place_extraction").get_active() + } + + client.extractor.set_config(config) + + def on_show_prefs(self): + if client.is_localhost(): + self.glade.get_widget("folderchooser_path").show() + self.glade.get_widget("entry_path").hide() + else: + self.glade.get_widget("folderchooser_path").hide() + self.glade.get_widget("entry_path").show() + + def on_get_config(config): + if client.is_localhost(): + self.glade.get_widget("folderchooser_path").set_current_folder(config["extract_path"]) + else: + self.glade.get_widget("entry_path").set_text(config["extract_path"]) + + self.glade.get_widget("chk_use_name").set_active(config["use_name_folder"]) + self.glade.get_widget("chk_in_place_extraction").set_active(config["in_place_extraction"]) + + client.extractor.get_config().addCallback(on_get_config) diff --git a/simpleextractor/webui.py b/simpleextractor/webui.py new file mode 100644 index 0000000..003c119 --- /dev/null +++ b/simpleextractor/webui.py @@ -0,0 +1,55 @@ +# +# webui.py +# +# Copyright (C) 2009 Andrew Resch +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# Deluge is free software. +# +# You may 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. +# +# deluge 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 deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# this exception statement from your version. If you delete this exception +# statement from all source files in the program, then also delete it here. +# +# + +from deluge.log import LOG as log +from deluge.ui.client import client +from deluge import component +from deluge.plugins.pluginbase import WebPluginBase + +from common import get_resource + +class WebUI(WebPluginBase): + def enable(self): + pass + + def disable(self): + pass + + scripts = [get_resource("simpleextractor.js")] + debug_scripts = scripts diff --git a/simpleextractor/which.py b/simpleextractor/which.py new file mode 100644 index 0000000..a41a529 --- /dev/null +++ b/simpleextractor/which.py @@ -0,0 +1,20 @@ +""" +which.py : Same as the Unix "which" command: tests if an executable exists and returns name +""" +def which(program): + # Author Credit: Jay @ http://stackoverflow.com/a/377028 + import os + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + + return None