diff --git a/deluge/plugins/autoadd/autoadd/__init__.py b/deluge/plugins/autoadd/autoadd/__init__.py new file mode 100644 index 000000000..813e7e3c8 --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/__init__.py @@ -0,0 +1,58 @@ +# +# __init__.py +# +# Copyright (C) 2009 GazpachoKing +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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/deluge/plugins/autoadd/autoadd/common.py b/deluge/plugins/autoadd/autoadd/common.py new file mode 100644 index 000000000..16172a975 --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/common.py @@ -0,0 +1,42 @@ +# +# common.py +# +# Copyright (C) 2009 GazpachoKing +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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("autoadd", os.path.join("data", filename)) diff --git a/deluge/plugins/autoadd/autoadd/core.py b/deluge/plugins/autoadd/autoadd/core.py new file mode 100644 index 000000000..dbab959fb --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/core.py @@ -0,0 +1,316 @@ +# +# core.py +# +# Copyright (C) 2009 GazpachoKing +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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._libtorrent import lt +import os +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 twisted.internet.task import LoopingCall, deferLater +from twisted.internet import reactor +from deluge.event import DelugeEvent + +DEFAULT_PREFS = { + "watchdirs":{}, + "next_id":1 +} + +OPTIONS_AVAILABLE = { #option: builtin + "enabled":False, + "path":False, + "abspath":False, + "download_location":True, + "max_download_speed":True, + "max_upload_speed":True, + "max_connections":True, + "max_upload_slots":True, + "prioritize_first_last":True, + "auto_managed":True, + "stop_at_ratio":True, + "stop_ratio":True, + "remove_at_ratio":True, + "move_completed":True, + "move_completed_path":True, + "label":False, + "add_paused":True +} + +MAX_NUM_ATTEMPTS = 10 + +class AutoaddOptionsChangedEvent(DelugeEvent): + """ + Emitted when a new command is added. + """ + def __init__(self): + pass + +def CheckInput(cond, message): + if not cond: + raise Exception(message) + +class Core(CorePluginBase): + def enable(self): + + #reduce typing, assigning some values to self... + self.config = deluge.configmanager.ConfigManager("autoadd.conf", DEFAULT_PREFS) + self.watchdirs = self.config["watchdirs"] + self.core_cfg = deluge.configmanager.ConfigManager("core.conf") + + + # A list of filenames + self.invalid_torrents = [] + # Filename:Attempts + self.attempts = {} + # Loopingcall timers for each enabled watchdir + self.update_timers = {} + deferLater(reactor, 5, self.enable_looping) + + def enable_looping(self): + #Enable all looping calls for enabled watchdirs here + for watchdir_id, watchdir in self.watchdirs.iteritems(): + if watchdir['enabled']: + self.enable_watchdir(watchdir_id) + + def disable(self): + #disable all running looping calls + for loopingcall in self.update_timers.itervalues(): + loopingcall.stop() + self.config.save() + pass + + def update(self): + pass + + @export() + def set_options(self, watchdir_id, options): + """ + update the options for a watch folder + """ + watchdir_id = str(watchdir_id) + options = self._clean_unicode(options) + CheckInput(watchdir_id in self.watchdirs , _("Watch folder does not exist.")) + if options.has_key('path'): + options['abspath'] = os.path.abspath(options['path']) + CheckInput(os.path.isdir(options['abspath']), _("Path does not exist.")) + for w_id, w in self.watchdirs.iteritems(): + if options['abspath'] == w['abspath'] and watchdir_id != w_id: + raise Exception("Path is already being watched.") + for key in options.keys(): + if not key in OPTIONS_AVAILABLE: + if not key in [key+'_toggle' for key in OPTIONS_AVAILABLE.iterkeys()]: + raise Exception("autoadd: Invalid options key:%s" % key) + #disable the watch loop if it was active + if watchdir_id in self.update_timers: + self.disable_watchdir(watchdir_id) + + self.watchdirs[watchdir_id].update(options) + #re-enable watch loop if appropriate + if self.watchdirs[watchdir_id]['enabled']: + self.enable_watchdir(watchdir_id) + self.config.save() + component.get("EventManager").emit(AutoaddOptionsChangedEvent()) + + def load_torrent(self, filename): + try: + log.debug("Attempting to open %s for add.", filename) + _file = open(filename, "rb") + filedump = _file.read() + if not filedump: + raise RuntimeError, "Torrent is 0 bytes!" + _file.close() + except IOError, e: + log.warning("Unable to open %s: %s", filename, e) + raise e + + # Get the info to see if any exceptions are raised + info = lt.torrent_info(lt.bdecode(filedump)) + + return filedump + + def update_watchdir(self, watchdir_id): + watchdir_id = str(watchdir_id) + watchdir = self.watchdirs[watchdir_id] + if not watchdir['enabled']: + # We shouldn't be updating because this watchdir is not enabled + #disable the looping call + self.update_timers[watchdir_id].stop() + del self.update_timers[watchdir_id] + return + + # Check the auto add folder for new torrents to add + if not os.path.isdir(watchdir["abspath"]): + log.warning("Invalid AutoAdd folder: %s", watchdir["abspath"]) + #disable the looping call + watchdir['enabled'] = False + self.update_timers[watchdir_id].stop() + del self.update_timers[watchdir_id] + return + + #Generate options dict for watchdir + opts={} + if watchdir.get('stop_at_ratio_toggle'): + watchdir['stop_ratio_toggle'] = True + for option, value in watchdir.iteritems(): + if OPTIONS_AVAILABLE.get(option): + if watchdir.get(option+'_toggle', True): + opts[option] = value + opts = self._clean_unicode(opts) + for filename in os.listdir(watchdir["abspath"]): + if filename.split(".")[-1] == "torrent": + try: + filepath = os.path.join(watchdir["abspath"], filename) + except UnicodeDecodeError, e: + log.error("Unable to auto add torrent due to inproper filename encoding: %s", e) + continue + try: + filedump = self.load_torrent(filepath) + except (RuntimeError, Exception), e: + # If the torrent is invalid, we keep track of it so that we + # can try again on the next pass. This is because some + # torrents may not be fully saved during the pass. + log.debug("Torrent is invalid: %s", e) + if filename in self.invalid_torrents: + self.attempts[filename] += 1 + if self.attempts[filename] >= MAX_NUM_ATTEMPTS: + os.rename(filepath, filepath + ".invalid") + del self.attempts[filename] + self.invalid_torrents.remove(filename) + else: + self.invalid_torrents.append(filename) + self.attempts[filename] = 1 + continue + + # The torrent looks good, so lets add it to the session + torrent_id = component.get("TorrentManager").add(filedump=filedump, filename=filename, options=opts) + if ('Label' in component.get("CorePluginManager").get_enabled_plugins()) and torrent_id: + if watchdir.get('label_toggle', True) and watchdir.get('label'): + label = component.get("CorePlugin.Label") + if not watchdir['label'] in label.get_labels(): + label.add(watchdir['label']) + label.set_torrent(torrent_id, watchdir['label']) + os.remove(filepath) + + @export + def enable_watchdir(self, watchdir_id): + watchdir_id = str(watchdir_id) + self.watchdirs[watchdir_id]['enabled'] = True + #If deluge core autoadd is enabled for this path, disable it + #log.error(self.core_cfg['autoadd_enable']) + if self.core_cfg.config.get('autoadd_enable'): + log.error("glob: %s loc: %s" % (os.path.abspath(self.core_cfg['autoadd_location']), self.watchdirs[watchdir_id]['abspath'])) + if os.path.abspath(self.core_cfg['autoadd_location']) == self.watchdirs[watchdir_id]['abspath']: + self.core_cfg['autoadd_enable'] = False + #Enable the looping call + self.update_timers[watchdir_id] = LoopingCall(self.update_watchdir, watchdir_id) + self.update_timers[watchdir_id].start(5) + self.config.save() + component.get("EventManager").emit(AutoaddOptionsChangedEvent()) + + @export + def disable_watchdir(self, watchdir_id): + watchdir_id = str(watchdir_id) + self.watchdirs[watchdir_id]['enabled'] = False + #disable the looping call here + self.update_timers[watchdir_id].stop() + del self.update_timers[watchdir_id] + self.config.save() + component.get("EventManager").emit(AutoaddOptionsChangedEvent()) + + @export + def set_config(self, config): + """sets the config dictionary""" + for key in config.keys(): + self.config[key] = config[key] + self.config.save() + component.get("EventManager").emit(AutoaddOptionsChangedEvent()) + + @export + def get_config(self): + """returns the config dictionary""" + return self.config.config + + @export() + def get_watchdirs(self): + return self.watchdirs.keys() + + def _clean_unicode(self, options): + opts = {} + for key, value in options.iteritems(): + if isinstance(key, unicode): + key = str(key) + if isinstance(value, unicode): + value = str(value) + opts[key] = value + return opts + + #Labels: + @export() + def add(self, options={}): + """add a watchdir + """ + options = self._clean_unicode(options) + abswatchdir = os.path.abspath(options['path']) + CheckInput(os.path.isdir(abswatchdir) , _("Path does not exist.")) + CheckInput(os.access(abswatchdir, os.R_OK|os.W_OK), "You must have read and write access to watch folder.") + for watchdir_id, watchdir in self.watchdirs.iteritems(): + if watchdir['abspath'] == abswatchdir: + raise Exception("Path is already being watched.") + options.setdefault('enabled', False) + options['abspath'] = abswatchdir + watchdir_id = self.config['next_id'] + self.watchdirs[str(watchdir_id)] = options + if options.get('enabled'): + self.enable_watchdir(watchdir_id) + self.config['next_id'] = watchdir_id + 1 + self.config.save() + component.get("EventManager").emit(AutoaddOptionsChangedEvent()) + return watchdir_id + + @export + def remove(self, watchdir_id): + """remove a label""" + watchdir_id = str(watchdir_id) + CheckInput(watchdir_id in self.watchdirs, "Unknown Watchdir: %s" % self.watchdirs) + if self.watchdirs[watchdir_id]['enabled']: + self.disable_watchdir(watchdir_id) + del self.watchdirs[watchdir_id] + self.config.save() + component.get("EventManager").emit(AutoaddOptionsChangedEvent()) diff --git a/deluge/plugins/autoadd/autoadd/data/autoadd.js b/deluge/plugins/autoadd/autoadd/data/autoadd.js new file mode 100644 index 000000000..f8f004f01 --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/data/autoadd.js @@ -0,0 +1,50 @@ +/* +Script: autoadd.js + The client-side javascript code for the AutoAdd plugin. + +Copyright: + (C) GazpachoKing 2009 + 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, 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, 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. +*/ + +AutoAddPlugin = Ext.extend(Deluge.Plugin, { + constructor: function(config) { + config = Ext.apply({ + name: "AutoAdd" + }, config); + AutoAddPlugin.superclass.constructor.call(this, config); + }, + + onDisable: function() { + + }, + + onEnable: function() { + + } +}); +new AutoAddPlugin(); diff --git a/deluge/plugins/autoadd/autoadd/data/autoadd_options.glade b/deluge/plugins/autoadd/autoadd/data/autoadd_options.glade new file mode 100644 index 000000000..a799b9f1c --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/data/autoadd_options.glade @@ -0,0 +1,986 @@ + + + + + + 6 + AutoAdd Error + False + True + dialog + + + + True + + + True + + + True + gtk-dialog-error + + + False + 0 + + + + + True + 0.46000000834465027 + Error + True + + + False + False + 1 + + + + + 2 + + + + + True + end + + + gtk-ok + -5 + True + True + True + False + True + + + + False + False + 0 + + + + + False + end + 0 + + + + + + + Watch Folder Properties + False + True + dialog + + + + True + + + True + + + True + True + + + True + 6 + + + True + 0 + none + + + True + 12 + + + True + + + True + + + True + True + + + + 0 + + + + + True + select-folder + Select A Folder + + + 1 + + + + + 0 + + + + + Enable this watch folder + True + True + False + True + True + + + + False + False + 1 + + + + + + + + + True + <b>Watch Folder</b> + True + + + label_item + + + + + False + 0 + + + + + True + 0 + none + + + True + 12 + + + True + + + Set download location + True + True + False + True + True + + + + False + False + 0 + + + + + True + + + True + True + + + + 0 + + + + + True + select-folder + Select A Folder + + + 1 + + + + + False + 1 + + + + + + + + + True + <b>Download Location</b> + True + + + label_item + + + + + False + 1 + + + + + True + 0 + none + + + True + 12 + + + True + + + Set move completed location + True + True + False + True + True + + + + False + False + 0 + + + + + True + + + True + True + + + + 0 + + + + + True + select-folder + Select A Folder + + + 1 + + + + + False + False + False + True + True + True + + + False + False + 2 + + + + + False + 1 + + + + + + + + + True + <b>Move Completed</b> + True + + + label_item + + + + + False + 2 + + + + + 0 + none + + + True + 12 + + + True + + + Label: + True + True + False + True + True + + + + False + False + 0 + + + + + True + True + + + + 1 + + + + + + + + + True + <b>Label</b> + True + + + label_item + + + + + 3 + + + + + + + True + Main + + + False + tab + + + + + True + 6 + + + + + + True + 0 + none + + + True + 12 + + + True + 3 + 4 + 3 + + + Max Upload Speed: + True + True + False + True + True + + + + 1 + 2 + GTK_FILL + + + + + + Max Connections: + True + True + False + True + True + + + + 2 + 3 + GTK_FILL + + + + + + Max Upload Slots: + True + True + False + True + True + + + + 3 + 4 + GTK_FILL + + + + + + True + True + -1 -1 10000 1 10 10 + 1 + 1 + + + 1 + 2 + + + + + + True + True + -1 -1 10000 1 10 10 + 1 + 1 + + + 1 + 2 + 1 + 2 + + + + + + True + True + -1 -1 10000 1 10 10 + 1 + + + 1 + 2 + 2 + 3 + + + + + + True + True + -1 -1 10000 1 10 10 + 1 + + + 1 + 2 + 3 + 4 + + + + + + True + 0 + 5 + KiB/s + + + 2 + 3 + GTK_FILL + + + + + + True + 0 + 5 + KiB/s + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Max Download Speed: + True + True + False + True + True + + + + GTK_FILL + + + + + + + + + + + + + + + + True + <b>Bandwidth</b> + True + + + label_item + + + + + 1 + + + + + True + 0 + none + + + True + 12 + + + True + 5 + 4 + + + True + + + Stop seed at ratio: + True + True + False + True + True + + + + + + 2 + 3 + GTK_FILL + + + + + True + 12 + + + Remove at ratio + True + True + False + True + True + + + + + 3 + 4 + + + + + Auto Managed: + True + True + False + True + True + + + + 1 + 2 + GTK_FILL + + + + + + False + True + False + True + True + True + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + False + True + False + True + True + True + + + 1 + 2 + 3 + 4 + GTK_FILL + + + + + + True + True + + 2 0 100 0.10000000149 10 10 + 1 + 1 + + + 1 + 2 + 2 + 3 + + + + + + True + + + Yes + True + True + False + True + True + + + 0 + + + + + No + True + True + False + True + auto_managed + + + 1 + + + + + 1 + 2 + 1 + 2 + GTK_FILL + GTK_FILL + + + + + False + True + False + True + True + True + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Add Paused: + True + True + False + True + + + + + + True + + + Yes + True + True + False + True + True + + + 0 + + + + + No + True + True + False + True + True + add_paused + + + 1 + + + + + 1 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + <b>Queue</b> + True + + + label_item + + + + + 2 + + + + + 1 + + + + + True + Options + + + 1 + False + tab + + + + + 0 + + + + + True + + + 1 + + + + + 2 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + + False + False + 0 + + + + + gtk-add + True + True + True + False + True + + + + False + False + 1 + + + + + gtk-apply + True + True + True + False + True + + + + False + False + 2 + + + + + False + end + 0 + + + + + + diff --git a/deluge/plugins/autoadd/autoadd/data/config.glade b/deluge/plugins/autoadd/autoadd/data/config.glade new file mode 100644 index 000000000..cb8cb6c1d --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/data/config.glade @@ -0,0 +1,116 @@ + + + + + + + + True + + + + + + True + + + 340 + 390 + True + 3 + vertical + + + + True + 0 + none + + + True + vertical + True + + + + + + + + True + <b>Watch Folders:</b> + True + + + label_item + + + + + 0 + + + + + True + + + gtk-add + True + True + True + True + True + + + + 0 + + + + + gtk-remove + True + False + True + True + True + True + + + + 1 + + + + + gtk-edit + True + False + True + True + True + True + + + + 2 + + + + + False + 1 + + + + + + + 1 + + + + + + diff --git a/deluge/plugins/autoadd/autoadd/gtkui.py b/deluge/plugins/autoadd/autoadd/gtkui.py new file mode 100644 index 000000000..3ade886c8 --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/gtkui.py @@ -0,0 +1,341 @@ +# +# gtkui.py +# +# Copyright (C) 2009 GazpachoKing +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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 +import os + +from common import get_resource + +class OptionsDialog(): + spin_ids = ["max_download_speed", "max_upload_speed", "stop_ratio"] + spin_int_ids = ["max_upload_slots", "max_connections"] + chk_ids = ["stop_at_ratio", "remove_at_ratio", "move_completed", "add_paused", "auto_managed"] + def __init__(self): + pass + + + def show(self, options={}, watchdir_id=None): + self.glade = gtk.glade.XML(get_resource("autoadd_options.glade")) + self.glade.signal_autoconnect({ + "on_opts_add":self.on_add, + "on_opts_apply":self.on_apply, + "on_opts_cancel":self.on_cancel, + "on_options_dialog_close":self.on_cancel, + "on_error_ok":self.on_error_ok, + "on_error_dialog_close":self.on_error_ok, + "on_toggle_toggled":self.on_toggle_toggled + }) + self.dialog = self.glade.get_widget("options_dialog") + self.dialog.set_transient_for(component.get("Preferences").pref_dialog) + self.err_dialog = self.glade.get_widget("error_dialog") + self.err_dialog.set_transient_for(self.dialog) + if watchdir_id: + #We have an existing watchdir_id, we are editing + self.glade.get_widget('opts_add_button').hide() + self.glade.get_widget('opts_apply_button').show() + self.watchdir_id = watchdir_id + else: + #We don't have an id, adding + self.glade.get_widget('opts_add_button').show() + self.glade.get_widget('opts_apply_button').hide() + self.watchdir_id = None + + self.load_options(options) + self.dialog.run() + + def load_options(self, options): + self.glade.get_widget('enabled').set_active(options.get('enabled', False)) + self.glade.get_widget('download_location_toggle').set_active(options.get('download_location_toggle', False)) + self.glade.get_widget('label').set_text(options.get('label', '')) + self.glade.get_widget('label_toggle').set_active(options.get('label_toggle', False)) + for id in self.spin_ids + self.spin_int_ids: + self.glade.get_widget(id).set_value(options.get(id, 0)) + self.glade.get_widget(id+'_toggle').set_active(options.get(id+'_toggle', False)) + for id in self.chk_ids: + self.glade.get_widget(id).set_active(bool(options.get(id, True))) + self.glade.get_widget(id+'_toggle').set_active(options.get(id+'_toggle', False)) + if not options.get('add_paused', True): + self.glade.get_widget('isnt_add_paused').set_active(True) + if not options.get('auto_managed', True): + self.glade.get_widget('isnt_auto_managed').set_active(True) + for field in ['move_completed_path', 'path', 'download_location']: + if client.is_localhost(): + self.glade.get_widget(field+"_chooser").set_filename(options.get(field, os.path.expanduser("~"))) + self.glade.get_widget(field+"_chooser").show() + self.glade.get_widget(field+"_entry").hide() + else: + self.glade.get_widget(field+"_entry").set_text(options.get(field, "")) + self.glade.get_widget(field+"_entry").show() + self.glade.get_widget(field+"_chooser").hide() + self.set_sensitive() + + def on_get_enabled_plugins(result): + if 'Label' in result: + self.glade.get_widget('label_frame').show() + else: + self.glade.get_widget('label_frame').hide() + self.glade.get_widget('label_toggle').set_active(False) + + client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins) + + def set_sensitive(self): + maintoggles = ['download_location', 'move_completed', 'label', \ + 'max_download_speed', 'max_upload_speed', 'max_connections', \ + 'max_upload_slots', 'add_paused', 'auto_managed', 'stop_at_ratio'] + [self.on_toggle_toggled(self.glade.get_widget(x+'_toggle')) for x in maintoggles] + + def on_toggle_toggled(self, tb): + toggle = str(tb.name).replace("_toggle", "") + isactive = tb.get_active() + if toggle == 'download_location': + self.glade.get_widget('download_location_chooser').set_sensitive(isactive) + self.glade.get_widget('download_location_entry').set_sensitive(isactive) + elif toggle == 'move_completed': + self.glade.get_widget('move_completed_path_chooser').set_sensitive(isactive) + self.glade.get_widget('move_completed_path_entry').set_sensitive(isactive) + self.glade.get_widget('move_completed').set_active(isactive) + elif toggle == 'label': + self.glade.get_widget('label').set_sensitive(isactive) + elif toggle == 'max_download_speed': + self.glade.get_widget('max_download_speed').set_sensitive(isactive) + elif toggle == 'max_upload_speed': + self.glade.get_widget('max_upload_speed').set_sensitive(isactive) + elif toggle == 'max_connections': + self.glade.get_widget('max_connections').set_sensitive(isactive) + elif toggle == 'max_upload_slots': + self.glade.get_widget('max_upload_slots').set_sensitive(isactive) + elif toggle == 'add_paused': + self.glade.get_widget('add_paused').set_sensitive(isactive) + self.glade.get_widget('isnt_add_paused').set_sensitive(isactive) + elif toggle == 'auto_managed': + self.glade.get_widget('auto_managed').set_sensitive(isactive) + self.glade.get_widget('isnt_auto_managed').set_sensitive(isactive) + elif toggle == 'stop_at_ratio': + self.glade.get_widget('remove_at_ratio_toggle').set_active(isactive) + self.glade.get_widget('stop_ratio_toggle').set_active(isactive) + self.glade.get_widget('stop_at_ratio').set_active(isactive) + self.glade.get_widget('stop_ratio').set_sensitive(isactive) + self.glade.get_widget('remove_at_ratio').set_sensitive(isactive) + + def on_apply(self, Event=None): + client.autoadd.set_options(str(self.watchdir_id), self.generate_opts()).addCallbacks(self.on_added, self.on_error_show) + + def on_error_show(self, result): + self.glade.get_widget('error_label').set_text(result.value.exception_msg) + self.err_dialog = self.glade.get_widget('error_dialog') + self.err_dialog.set_transient_for(self.dialog) + result.cleanFailure() + self.err_dialog.show() + + def on_added(self, result): + self.dialog.destroy() + + def on_error_ok(self, Event=None): + self.err_dialog.hide() + + def on_add(self, Event=None): + client.autoadd.add(self.generate_opts()).addCallbacks(self.on_added, self.on_error_show) + + def on_cancel(self, Event=None): + self.dialog.destroy() + + def generate_opts(self): + # generate options dict based on gtk objects + options = {} + options['enabled'] = self.glade.get_widget('enabled').get_active() + if client.is_localhost(): + options['path'] = self.glade.get_widget('path_chooser').get_filename() + options['download_location'] = self.glade.get_widget('download_location_chooser').get_filename() + options['move_completed_path'] = self.glade.get_widget('move_completed_path_chooser').get_filename() + else: + options['path'] = self.glade.get_widget('path_entry').get_text() + options['download_location'] = self.glade.get_widget('download_location_entry').get_text() + options['move_completed_path'] = self.glade.get_widget('move_completed_path_entry').get_text() + options['download_location_toggle'] = self.glade.get_widget('download_location_toggle').get_active() + options['label'] = self.glade.get_widget('label').get_text().lower() + options['label_toggle'] = self.glade.get_widget('label_toggle').get_active() + + + for id in self.spin_ids: + options[id] = self.glade.get_widget(id).get_value() + options[id+'_toggle'] = self.glade.get_widget(id+'_toggle').get_active() + for id in self.spin_int_ids: + options[id] = self.glade.get_widget(id).get_value_as_int() + options[id+'_toggle'] = self.glade.get_widget(id+'_toggle').get_active() + for id in self.chk_ids: + options[id] = self.glade.get_widget(id).get_active() + options[id+'_toggle'] = self.glade.get_widget(id+'_toggle').get_active() + return options + + +class GtkUI(GtkPluginBase): + def enable(self): + + self.glade = gtk.glade.XML(get_resource("config.glade")) + self.glade.signal_autoconnect({ + "on_add_button_clicked": self.on_add_button_clicked, + "on_edit_button_clicked": self.on_edit_button_clicked, + "on_remove_button_clicked": self.on_remove_button_clicked + }) + self.opts_dialog = OptionsDialog() + + component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs) + component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs) + client.register_event_handler("AutoaddOptionsChangedEvent", self.on_options_changed_event) + + self.watchdirs = {} + + vbox = self.glade.get_widget("watchdirs_vbox") + sw = gtk.ScrolledWindow() + sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + + vbox.pack_start(sw, True, True, 0) + + self.store = self.create_model() + + self.treeView = gtk.TreeView(self.store) + self.treeView.connect("cursor-changed", self.on_listitem_activated) + self.treeView.connect("row-activated", self.on_edit_button_clicked) + self.treeView.set_rules_hint(True) + + self.create_columns(self.treeView) + sw.add(self.treeView) + sw.show_all() + component.get("Preferences").add_page("AutoAdd", self.glade.get_widget("prefs_box")) + + + def disable(self): + component.get("Preferences").remove_page("AutoAdd") + component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs) + component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs) + + def create_model(self): + + store = gtk.ListStore(str, bool, str) + for watchdir_id, watchdir in self.watchdirs.iteritems(): + store.append([watchdir_id, watchdir['enabled'], watchdir['path']]) + return store + + def create_columns(self, treeView): + rendererToggle = gtk.CellRendererToggle() + column = gtk.TreeViewColumn("On", rendererToggle, activatable=True, active=1) + column.set_sort_column_id(1) + treeView.append_column(column) + tt = gtk.Tooltip() + tt.set_text('Double-click to toggle') + treeView.set_tooltip_cell(tt, None, None, rendererToggle) + + rendererText = gtk.CellRendererText() + column = gtk.TreeViewColumn("Path", rendererText, text=2) + column.set_sort_column_id(2) + treeView.append_column(column) + tt2 = gtk.Tooltip() + tt2.set_text('Double-click to edit') + #treeView.set_tooltip_cell(tt2, None, column, None) + treeView.set_has_tooltip(True) + + def load_watchdir_list(self): + pass + + def add_watchdir_entry(self): + pass + + def on_add_button_clicked(self, Event=None): + #display options_window + self.opts_dialog.show() + + def on_remove_button_clicked(self, Event=None): + tree, tree_id = self.treeView.get_selection().get_selected() + watchdir_id = str(self.store.get_value(tree_id, 0)) + if watchdir_id: + client.autoadd.remove(watchdir_id) + + def on_edit_button_clicked(self, Event=None, a=None, col=None): + tree, tree_id = self.treeView.get_selection().get_selected() + watchdir_id = str(self.store.get_value(tree_id, 0)) + if watchdir_id: + if col and col.get_title() == 'On': + if self.watchdirs[watchdir_id]['enabled']: + client.autoadd.disable_watchdir(watchdir_id) + else: + client.autoadd.enable_watchdir(watchdir_id) + else: + self.opts_dialog.show(self.watchdirs[watchdir_id], watchdir_id) + + def on_listitem_activated(self, treeview): + tree, tree_id = self.treeView.get_selection().get_selected() + if tree_id: + self.glade.get_widget('edit_button').set_sensitive(True) + self.glade.get_widget('remove_button').set_sensitive(True) + else: + self.glade.get_widget('edit_button').set_sensitive(False) + self.glade.get_widget('remove_button').set_sensitive(False) + + def on_apply_prefs(self): + log.debug("applying prefs for AutoAdd") + for watchdir_id, watchdir in self.watchdirs.iteritems(): + client.autoadd.set_options(watchdir_id, watchdir) + + def on_show_prefs(self): + + client.autoadd.get_config().addCallback(self.cb_get_config) + + def on_options_changed_event(self): + client.autoadd.get_config().addCallback(self.cb_get_config) + + def cb_get_config(self, config): + """callback for on show_prefs""" + self.watchdirs = config.get('watchdirs', {}) + self.store.clear() + for watchdir_id, watchdir in self.watchdirs.iteritems(): + self.store.append([watchdir_id, watchdir['enabled'], watchdir['path']]) + if False: #self.watchdirs: + self.glade.get_widget('remove_button').set_sensitive(True) + self.glade.get_widget('edit_button').set_sensitive(True) + else: + self.glade.get_widget('remove_button').set_sensitive(False) + self.glade.get_widget('edit_button').set_sensitive(False) + diff --git a/deluge/plugins/autoadd/autoadd/webui.py b/deluge/plugins/autoadd/autoadd/webui.py new file mode 100644 index 000000000..a09a819f3 --- /dev/null +++ b/deluge/plugins/autoadd/autoadd/webui.py @@ -0,0 +1,55 @@ +# +# webui.py +# +# Copyright (C) 2009 GazpachoKing +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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): + + scripts = [get_resource("autoadd.js")] + + def enable(self): + pass + + def disable(self): + pass diff --git a/deluge/plugins/autoadd/setup.py b/deluge/plugins/autoadd/setup.py new file mode 100644 index 000000000..6522a92fb --- /dev/null +++ b/deluge/plugins/autoadd/setup.py @@ -0,0 +1,73 @@ +# +# setup.py +# +# Copyright (C) 2009 GazpachoKing +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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__ = "AutoAdd" +__author__ = "Chase Sterling" +__author_email__ = "chase.sterling@gmail.com" +__version__ = "0.28" +__url__ = "http://forum.deluge-torrent.org/viewtopic.php?f=9&t=26775" +__license__ = "GPLv3" +__description__ = "Monitors folders for .torrent files." +__long_description__ = """""" +__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.webui] + %s = %s:WebUIPlugin + """ % ((__plugin_name__, __plugin_name__.lower())*3) +)