summaryrefslogtreecommitdiffstats
path: root/deluge/plugins/AutoAdd
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/plugins/AutoAdd')
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/__init__.py41
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/common.py24
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/core.py522
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js239
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js475
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui1342
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/main_tab.js304
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/options_tab.js302
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/config.ui134
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py580
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/webui.py38
-rw-r--r--deluge/plugins/AutoAdd/setup.py48
12 files changed, 4049 insertions, 0 deletions
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/__init__.py b/deluge/plugins/AutoAdd/deluge_autoadd/__init__.py
new file mode 100644
index 0000000..a409cfc
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/__init__.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2009 Damien Churchill <damoxc@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
+
+from deluge.plugins.init import PluginInitBase
+
+
+class CorePlugin(PluginInitBase):
+ def __init__(self, plugin_name):
+ from .core import Core as _pluginCls
+
+ self._plugin_cls = _pluginCls
+ super(CorePlugin, self).__init__(plugin_name)
+
+
+class Gtk3UIPlugin(PluginInitBase):
+ def __init__(self, plugin_name):
+ from .gtkui import GtkUI as _pluginCls
+
+ self._plugin_cls = _pluginCls
+ super(Gtk3UIPlugin, self).__init__(plugin_name)
+
+
+class WebUIPlugin(PluginInitBase):
+ def __init__(self, plugin_name):
+ from .webui import WebUI as _pluginCls
+
+ self._plugin_cls = _pluginCls
+ super(WebUIPlugin, self).__init__(plugin_name)
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/common.py b/deluge/plugins/AutoAdd/deluge_autoadd/common.py
new file mode 100644
index 0000000..9b4b1e7
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/common.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# 2007-2009 Andrew Resch <andrewresch@gmail.com>
+# 2009 Damien Churchill <damoxc@gmail.com>
+# 2010 Pedro Algarvio <pedro@algarvio.me>
+# 2017 Calum Lind <calumlind+deluge@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 os.path
+
+from pkg_resources import resource_filename
+
+
+def get_resource(filename, subdir=False):
+ folder = os.path.join('data', 'autoadd_options') if subdir else 'data'
+ return resource_filename(__package__, os.path.join(folder, filename))
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/core.py b/deluge/plugins/AutoAdd/deluge_autoadd/core.py
new file mode 100644
index 0000000..79e5327
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/core.py
@@ -0,0 +1,522 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
+# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2009 Damien Churchill <damoxc@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 logging
+import os
+import shutil
+from base64 import b64encode
+
+from twisted.internet import reactor
+from twisted.internet.task import LoopingCall, deferLater
+
+import deluge.component as component
+import deluge.configmanager
+from deluge._libtorrent import lt
+from deluge.common import AUTH_LEVEL_ADMIN, is_magnet
+from deluge.core.rpcserver import export
+from deluge.error import AddTorrentError
+from deluge.event import DelugeEvent
+from deluge.plugins.pluginbase import CorePluginBase
+
+log = logging.getLogger(__name__)
+
+
+DEFAULT_PREFS = {'watchdirs': {}, 'next_id': 1}
+
+
+OPTIONS_AVAILABLE = { # option: builtin
+ 'enabled': False,
+ 'path': False,
+ 'append_extension': False,
+ 'copy_torrent': False,
+ 'delete_copy_torrent_toggle': 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,
+ 'queue_to_top': False,
+ 'owner': True,
+ 'seed_mode': True,
+}
+
+MAX_NUM_ATTEMPTS = 10
+
+
+class AutoaddOptionsChangedEvent(DelugeEvent):
+ """Emitted when the options for the plugin are changed."""
+
+ def __init__(self):
+ pass
+
+
+def check_input(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.config.run_converter((0, 1), 2, self.__migrate_config_1_to_2)
+ self.config.save()
+ self.watchdirs = self.config['watchdirs']
+
+ self.rpcserver = component.get('RPCServer')
+ component.get('EventManager').register_event_handler(
+ 'PreTorrentRemovedEvent', self.__on_pre_torrent_removed
+ )
+
+ # Dict of Filename:Attempts
+ self.invalid_torrents = {}
+ # 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.items():
+ if watchdir['enabled']:
+ self.enable_watchdir(watchdir_id)
+
+ def disable(self):
+ # disable all running looping calls
+ component.get('EventManager').deregister_event_handler(
+ 'PreTorrentRemovedEvent', self.__on_pre_torrent_removed
+ )
+ for loopingcall in self.update_timers.values():
+ loopingcall.stop()
+ self.config.save()
+
+ 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._make_unicode(options)
+ check_input(watchdir_id in self.watchdirs, _('Watch folder does not exist.'))
+ if 'path' in options:
+ options['abspath'] = os.path.abspath(options['path'])
+ check_input(os.path.isdir(options['abspath']), _('Path does not exist.'))
+ for w_id, w in self.watchdirs.items():
+ if options['abspath'] == w['abspath'] and watchdir_id != w_id:
+ raise Exception('Path is already being watched.')
+ for key in options:
+ if key not in OPTIONS_AVAILABLE:
+ if key not in [key2 + '_toggle' for key2 in OPTIONS_AVAILABLE]:
+ 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, magnet):
+ log.debug('Attempting to open %s for add.', filename)
+ file_mode = 'r' if magnet else 'rb'
+ try:
+ with open(filename, file_mode) as _file:
+ filedump = _file.read()
+ except IOError as ex:
+ log.warning('Unable to open %s: %s', filename, ex)
+ raise ex
+
+ if not filedump:
+ raise EOFError('Torrent is 0 bytes!')
+
+ # Get the info to see if any exceptions are raised
+ if not magnet:
+ lt.torrent_info(lt.bdecode(filedump))
+
+ return filedump
+
+ def split_magnets(self, filename):
+ log.debug('Attempting to open %s for splitting magnets.', filename)
+ magnets = []
+ try:
+ with open(filename, 'r') as _file:
+ magnets = list(filter(len, _file.read().splitlines()))
+ except IOError as ex:
+ log.warning('Unable to open %s: %s', filename, ex)
+
+ if len(magnets) < 2:
+ return []
+
+ path = filename.rsplit(os.sep, 1)[0]
+ for magnet in magnets:
+ if not is_magnet(magnet):
+ log.warning('Found line which is not a magnet: %s', magnet)
+ continue
+
+ for part in magnet.split('&'):
+ if part.startswith('dn='):
+ name = part[3:].strip()
+ if name:
+ mname = os.sep.join([path, name + '.magnet'])
+ break
+ else:
+ short_hash = magnet.split('btih:')[1][:8]
+ mname = '.'.join([os.path.splitext(filename)[0], short_hash, 'magnet'])
+
+ try:
+ with open(mname, 'w') as _mfile:
+ _mfile.write(magnet)
+ except IOError as ex:
+ log.warning('Unable to open %s: %s', mname, ex)
+ return magnets
+
+ def update_watchdir(self, watchdir_id):
+ """Check the watch folder for new torrents to add."""
+ log.trace('Updating watchdir id: %s', 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
+ log.debug('Watchdir id %s is not enabled. Disabling it.', watchdir_id)
+ self.disable_watchdir(watchdir_id)
+ return
+
+ if not os.path.isdir(watchdir['abspath']):
+ log.warning('Invalid AutoAdd folder: %s', watchdir['abspath'])
+ self.disable_watchdir(watchdir_id)
+ return
+
+ # Generate options dict for watchdir
+ options = {}
+ if 'stop_at_ratio_toggle' in watchdir:
+ watchdir['stop_ratio_toggle'] = watchdir['stop_at_ratio_toggle']
+ # We default to True when reading _toggle values, so a config
+ # without them is valid, and applies all its settings.
+ for option, value in watchdir.items():
+ if OPTIONS_AVAILABLE.get(option):
+ if watchdir.get(option + '_toggle', True) or option in [
+ 'owner',
+ 'seed_mode',
+ ]:
+ options[option] = value
+
+ # Check for .magnet files containing multiple magnet links and
+ # create a new .magnet file for each of them.
+ for filename in os.listdir(watchdir['abspath']):
+ try:
+ filepath = os.path.join(watchdir['abspath'], filename)
+ except UnicodeDecodeError as ex:
+ log.error(
+ 'Unable to auto add torrent due to improper filename encoding: %s',
+ ex,
+ )
+ continue
+ if os.path.isdir(filepath):
+ # Skip directories
+ continue
+ elif os.path.splitext(filename)[1] == '.magnet' and self.split_magnets(
+ filepath
+ ):
+ os.remove(filepath)
+
+ for filename in os.listdir(watchdir['abspath']):
+ try:
+ filepath = os.path.join(watchdir['abspath'], filename)
+ except UnicodeDecodeError as ex:
+ log.error(
+ 'Unable to auto add torrent due to improper filename encoding: %s',
+ ex,
+ )
+ continue
+
+ if os.path.isdir(filepath):
+ # Skip directories
+ continue
+
+ ext = os.path.splitext(filename)[1].lower()
+ magnet = ext == '.magnet'
+ if not magnet and not ext == '.torrent':
+ log.debug('File checked for auto-loading is invalid: %s', filename)
+ continue
+
+ try:
+ filedump = self.load_torrent(filepath, magnet)
+ except (IOError, EOFError) as ex:
+ # If torrent is invalid, keep track of it so can try again on the next pass.
+ # This catches torrent files that may not be fully saved to disk at load time.
+ log.debug('Torrent is invalid: %s', ex)
+ if filename in self.invalid_torrents:
+ self.invalid_torrents[filename] += 1
+ if self.invalid_torrents[filename] >= MAX_NUM_ATTEMPTS:
+ log.warning(
+ 'Maximum attempts reached while trying to add the '
+ 'torrent file with the path %s',
+ filepath,
+ )
+ os.rename(filepath, filepath + '.invalid')
+ del self.invalid_torrents[filename]
+ else:
+ self.invalid_torrents[filename] = 1
+ continue
+
+ def on_torrent_added(torrent_id, filename, filepath):
+ if 'Label' in component.get('CorePluginManager').get_enabled_plugins():
+ 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'])
+ try:
+ label.set_torrent(torrent_id, watchdir['label'])
+ except Exception as ex:
+ log.error('Unable to set label: %s', ex)
+
+ if (
+ watchdir.get('queue_to_top_toggle', True)
+ and 'queue_to_top' in watchdir
+ ):
+ if watchdir['queue_to_top']:
+ component.get('TorrentManager').queue_top(torrent_id)
+ else:
+ component.get('TorrentManager').queue_bottom(torrent_id)
+
+ # Rename, copy or delete the torrent once added to deluge.
+ if watchdir.get('append_extension_toggle'):
+ if not watchdir.get('append_extension'):
+ watchdir['append_extension'] = '.added'
+ os.rename(filepath, filepath + watchdir['append_extension'])
+ elif watchdir.get('copy_torrent_toggle'):
+ copy_torrent_path = watchdir['copy_torrent']
+ copy_torrent_file = os.path.join(copy_torrent_path, filename)
+ log.debug(
+ 'Moving added torrent file "%s" to "%s"',
+ os.path.basename(filepath),
+ copy_torrent_path,
+ )
+ shutil.move(filepath, copy_torrent_file)
+ else:
+ os.remove(filepath)
+
+ def fail_torrent_add(err_msg, filepath, magnet):
+ # torrent handle is invalid and so is the magnet link
+ log.error(
+ 'Cannot Autoadd %s: %s: %s',
+ 'magnet' if magnet else 'torrent file',
+ filepath,
+ err_msg,
+ )
+ os.rename(filepath, filepath + '.invalid')
+
+ try:
+ # The torrent looks good, so lets add it to the session.
+ if magnet:
+ d = component.get('Core').add_torrent_magnet(
+ filedump.strip(), options
+ )
+ else:
+ d = component.get('Core').add_torrent_file_async(
+ filename, b64encode(filedump), options
+ )
+ d.addCallback(on_torrent_added, filename, filepath)
+ d.addErrback(fail_torrent_add, filepath, magnet)
+ except AddTorrentError as ex:
+ fail_torrent_add(str(ex), filepath, magnet)
+
+ def on_update_watchdir_error(self, failure, watchdir_id):
+ """Disables any watch folders with un-handled exceptions."""
+ self.disable_watchdir(watchdir_id)
+ log.error(
+ 'Disabling "%s", error during update: %s',
+ self.watchdirs[watchdir_id]['path'],
+ failure,
+ )
+
+ @export
+ def enable_watchdir(self, watchdir_id):
+ w_id = str(watchdir_id)
+ # Enable the looping call
+ if w_id not in self.update_timers or not self.update_timers[w_id].running:
+ self.update_timers[w_id] = LoopingCall(self.update_watchdir, w_id)
+ self.update_timers[w_id].start(5).addErrback(
+ self.on_update_watchdir_error, w_id
+ )
+ # Update the config
+ if not self.watchdirs[w_id]['enabled']:
+ self.watchdirs[w_id]['enabled'] = True
+ self.config.save()
+ component.get('EventManager').emit(AutoaddOptionsChangedEvent())
+
+ @export
+ def disable_watchdir(self, watchdir_id):
+ w_id = str(watchdir_id)
+ # Disable the looping call
+ if w_id in self.update_timers:
+ if self.update_timers[w_id].running:
+ self.update_timers[w_id].stop()
+ del self.update_timers[w_id]
+ # Update the config
+ if self.watchdirs[w_id]['enabled']:
+ self.watchdirs[w_id]['enabled'] = False
+ self.config.save()
+ component.get('EventManager').emit(AutoaddOptionsChangedEvent())
+
+ @export
+ def set_config(self, config):
+ """Sets the config dictionary."""
+ config = self._make_unicode(config)
+ for key in config:
+ 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):
+ session_user = self.rpcserver.get_session_user()
+ session_auth_level = self.rpcserver.get_session_auth_level()
+ if session_auth_level == AUTH_LEVEL_ADMIN:
+ log.debug(
+ 'Current logged in user %s is an ADMIN, send all ' 'watchdirs',
+ session_user,
+ )
+ return self.watchdirs
+
+ watchdirs = {}
+ for watchdir_id, watchdir in self.watchdirs.items():
+ if watchdir.get('owner', 'localclient') == session_user:
+ watchdirs[watchdir_id] = watchdir
+
+ log.debug(
+ 'Current logged in user %s is not an ADMIN, send only '
+ 'their watchdirs: %s',
+ session_user,
+ list(watchdirs),
+ )
+ return watchdirs
+
+ def _make_unicode(self, options):
+ opts = {}
+ for key in options:
+ if isinstance(options[key], bytes):
+ options[key] = options[key].decode('utf8')
+ opts[key] = options[key]
+ return opts
+
+ @export
+ def add(self, options=None):
+ """Add a watch folder."""
+ if options is None:
+ options = {}
+ options = self._make_unicode(options)
+ abswatchdir = os.path.abspath(options['path'])
+ check_input(os.path.isdir(abswatchdir), _('Path does not exist.'))
+ check_input(
+ os.access(abswatchdir, os.R_OK | os.W_OK),
+ 'You must have read and write access to watch folder.',
+ )
+ if abswatchdir in [wd['abspath'] for wd in self.watchdirs.values()]:
+ 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 watch folder."""
+ watchdir_id = str(watchdir_id)
+ check_input(
+ 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())
+
+ def __migrate_config_1_to_2(self, config):
+ for watchdir_id in config['watchdirs']:
+ config['watchdirs'][watchdir_id]['owner'] = 'localclient'
+ return config
+
+ def __on_pre_torrent_removed(self, torrent_id):
+ try:
+ torrent = component.get('TorrentManager')[torrent_id]
+ except KeyError:
+ log.warning(
+ 'Unable to remove torrent file for torrent id %s. It'
+ 'was already deleted from the TorrentManager',
+ torrent_id,
+ )
+ return
+ torrent_fname = torrent.filename
+ for watchdir in self.watchdirs.values():
+ if not watchdir.get('copy_torrent_toggle', False):
+ # This watchlist does copy torrents
+ continue
+ elif not watchdir.get('delete_copy_torrent_toggle', False):
+ # This watchlist is not set to delete finished torrents
+ continue
+ copy_torrent_path = watchdir['copy_torrent']
+ torrent_fname_path = os.path.join(copy_torrent_path, torrent_fname)
+ if os.path.isfile(torrent_fname_path):
+ try:
+ os.remove(torrent_fname_path)
+ log.info(
+ 'Removed torrent file "%s" from "%s"',
+ torrent_fname,
+ copy_torrent_path,
+ )
+ break
+ except OSError as ex:
+ log.info(
+ 'Failed to removed torrent file "%s" from "%s": %s',
+ torrent_fname,
+ copy_torrent_path,
+ ex,
+ )
+
+ @export
+ def is_admin_level(self):
+ return self.rpcserver.get_session_auth_level() == deluge.common.AUTH_LEVEL_ADMIN
+
+ @export
+ def get_auth_user(self):
+ return self.rpcserver.get_session_user()
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js
new file mode 100644
index 0000000..40086b3
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js
@@ -0,0 +1,239 @@
+/**
+ * Script: autoadd.js
+ * The client-side javascript code for the AutoAdd plugin.
+ *
+ * Copyright (C) 2009 GazpachoKing <chase.sterling@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.
+ */
+
+Ext.ns('Deluge.ux.AutoAdd');
+Deluge.ux.AutoAdd.onClickFunctions = {};
+
+Ext.ns('Deluge.ux.preferences');
+
+/**
+ * @class Deluge.ux.preferences.AutoAddPage
+ * @extends Ext.Panel
+ */
+Deluge.ux.preferences.AutoAddPage = Ext.extend(Ext.Panel, {
+ title: _('AutoAdd'),
+ header: false,
+ layout: 'fit',
+ border: false,
+ watchdirs: {},
+
+ initComponent: function() {
+ Deluge.ux.preferences.AutoAddPage.superclass.initComponent.call(this);
+
+ var autoAdd = this;
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.JsonStore({
+ fields: ['id', 'enabled', 'owner', 'path'],
+ }),
+ columns: [
+ {
+ id: 'enabled',
+ header: _('Active'),
+ sortable: true,
+ dataIndex: 'enabled',
+ tpl: new Ext.XTemplate('{enabled:this.getCheckbox}', {
+ getCheckbox: function(checked, selected) {
+ Deluge.ux.AutoAdd.onClickFunctions[
+ selected.id
+ ] = function() {
+ if (selected.enabled) {
+ deluge.client.autoadd.disable_watchdir(
+ selected.id
+ );
+ checked = false;
+ } else {
+ deluge.client.autoadd.enable_watchdir(
+ selected.id
+ );
+ checked = true;
+ }
+ autoAdd.updateWatchDirs();
+ };
+ return (
+ '<input id="enabled-' +
+ selected.id +
+ '" type="checkbox"' +
+ (checked ? ' checked' : '') +
+ ' onclick="Deluge.ux.AutoAdd.onClickFunctions[' +
+ selected.id +
+ ']()" />'
+ );
+ },
+ }),
+ width: 0.15,
+ },
+ {
+ id: 'owner',
+ header: _('Owner'),
+ sortable: true,
+ dataIndex: 'owner',
+ width: 0.2,
+ },
+ {
+ id: 'path',
+ header: _('Path'),
+ sortable: true,
+ dataIndex: 'path',
+ },
+ ],
+ singleSelect: true,
+ autoExpandColumn: 'path',
+ });
+ this.list.on('selectionchange', this.onSelectionChange, this);
+
+ this.panel = this.add({
+ items: [this.list],
+ bbar: {
+ items: [
+ {
+ text: _('Add'),
+ iconCls: 'icon-add',
+ handler: this.onAddClick,
+ scope: this,
+ },
+ {
+ text: _('Edit'),
+ iconCls: 'icon-edit',
+ handler: this.onEditClick,
+ scope: this,
+ disabled: true,
+ },
+ '->',
+ {
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onRemoveClick,
+ scope: this,
+ disabled: true,
+ },
+ ],
+ },
+ });
+
+ this.on('show', this.onPreferencesShow, this);
+ },
+
+ updateWatchDirs: function() {
+ deluge.client.autoadd.get_watchdirs({
+ success: function(watchdirs) {
+ this.watchdirs = watchdirs;
+ var watchdirsArray = [];
+ for (var id in watchdirs) {
+ if (watchdirs.hasOwnProperty(id)) {
+ var watchdir = {};
+ watchdir['id'] = id;
+ watchdir['enabled'] = watchdirs[id].enabled;
+ watchdir['owner'] =
+ watchdirs[id].owner || 'localclient';
+ watchdir['path'] = watchdirs[id].path;
+
+ watchdirsArray.push(watchdir);
+ }
+ }
+ this.list.getStore().loadData(watchdirsArray);
+ },
+ scope: this,
+ });
+ },
+
+ onAddClick: function() {
+ if (!this.addWin) {
+ this.addWin = new Deluge.ux.AutoAdd.AddAutoAddCommandWindow();
+ this.addWin.on(
+ 'watchdiradd',
+ function() {
+ this.updateWatchDirs();
+ },
+ this
+ );
+ }
+ this.addWin.show();
+ },
+
+ onEditClick: function() {
+ if (!this.editWin) {
+ this.editWin = new Deluge.ux.AutoAdd.EditAutoAddCommandWindow();
+ this.editWin.on(
+ 'watchdiredit',
+ function() {
+ this.updateWatchDirs();
+ },
+ this
+ );
+ }
+ var id = this.list.getSelectedRecords()[0].id;
+ this.editWin.show(id, this.watchdirs[id]);
+ },
+
+ onPreferencesShow: function() {
+ this.updateWatchDirs();
+ },
+
+ onRemoveClick: function() {
+ var record = this.list.getSelectedRecords()[0];
+ deluge.client.autoadd.remove(record.id, {
+ success: function() {
+ this.updateWatchDirs();
+ },
+ scope: this,
+ });
+ },
+
+ onSelectionChange: function(dv, selections) {
+ if (selections.length) {
+ this.panel
+ .getBottomToolbar()
+ .items.get(1)
+ .enable();
+ this.panel
+ .getBottomToolbar()
+ .items.get(3)
+ .enable();
+ } else {
+ this.panel
+ .getBottomToolbar()
+ .items.get(1)
+ .disable();
+ this.panel
+ .getBottomToolbar()
+ .items.get(3)
+ .disable();
+ }
+ },
+});
+
+Deluge.plugins.AutoAddPlugin = Ext.extend(Deluge.Plugin, {
+ name: 'AutoAdd',
+
+ static: {
+ prefsPage: null,
+ },
+
+ onDisable: function() {
+ deluge.preferences.removePage(Deluge.plugins.AutoAddPlugin.prefsPage);
+ Deluge.plugins.AutoAddPlugin.prefsPage = null;
+ },
+
+ onEnable: function() {
+ /*
+ * Called for each of the JavaScript files.
+ * This will prevent adding unnecessary tabs to the preferences window.
+ */
+ if (!Deluge.plugins.AutoAddPlugin.prefsPage) {
+ Deluge.plugins.AutoAddPlugin.prefsPage = deluge.preferences.addPage(
+ new Deluge.ux.preferences.AutoAddPage()
+ );
+ }
+ },
+});
+
+Deluge.registerPlugin('AutoAdd', Deluge.plugins.AutoAddPlugin);
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js
new file mode 100644
index 0000000..49f752f
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js
@@ -0,0 +1,475 @@
+/**
+ * Script: autoadd.js
+ * The client-side javascript code for the AutoAdd plugin.
+ *
+ * Copyright (C) 2009 GazpachoKing <chase.sterling@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.
+ */
+
+Ext.ns('Deluge.ux.AutoAdd');
+
+/**
+ * @class Deluge.ux.AutoAdd.AutoAddWindowBase
+ * @extends Ext.Window
+ */
+Deluge.ux.AutoAdd.AutoAddWindowBase = Ext.extend(Ext.Window, {
+ width: 350,
+ autoHeight: true,
+ closeAction: 'hide',
+
+ 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',
+ 'queue_to_top',
+ ],
+ toggle_ids: [
+ 'append_extension_toggle',
+ 'download_location_toggle',
+ 'label_toggle',
+ 'copy_torrent_toggle',
+ 'delete_copy_torrent_toggle',
+ 'seed_mode',
+ ],
+
+ accounts: new Ext.data.ArrayStore({
+ storeId: 'accountStore',
+ id: 0,
+ fields: [
+ {
+ name: 'displayText',
+ type: 'string',
+ },
+ ],
+ }),
+ labels: new Ext.data.ArrayStore({
+ storeId: 'labelStore',
+ id: 0,
+ fields: [
+ {
+ name: 'displayText',
+ type: 'string',
+ },
+ ],
+ }),
+
+ initComponent: function() {
+ Deluge.ux.AutoAdd.AutoAddWindowBase.superclass.initComponent.call(this);
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+
+ this.MainTab = new Deluge.ux.AutoAdd.AutoAddMainPanel();
+ this.OptionsTab = new Deluge.ux.AutoAdd.AutoAddOptionsPanel();
+
+ this.form = this.add({
+ xtype: 'form',
+ baseCls: 'x-plain',
+ bodyStyle: 'padding: 5px',
+ items: [
+ {
+ xtype: 'tabpanel',
+ activeTab: 0,
+ items: [this.MainTab, this.OptionsTab],
+ },
+ ],
+ });
+ },
+
+ onCancelClick: function() {
+ this.hide();
+ },
+
+ getOptions: function() {
+ var options = {};
+
+ options['enabled'] = Ext.getCmp('enabled').getValue();
+ options['path'] = Ext.getCmp('path').getValue();
+ options['download_location'] = Ext.getCmp(
+ 'download_location'
+ ).getValue();
+ options['move_completed_path'] = Ext.getCmp(
+ 'move_completed_path'
+ ).getValue();
+ options['copy_torrent'] = Ext.getCmp('copy_torrent').getValue();
+
+ options['label'] = Ext.getCmp('label').getValue();
+ options['append_extension'] = Ext.getCmp('append_extension').getValue();
+ options['owner'] = Ext.getCmp('owner').getValue();
+
+ this.toggle_ids.forEach(function(toggle_id) {
+ options[toggle_id] = Ext.getCmp(toggle_id).getValue();
+ });
+ this.spin_ids.forEach(function(spin_id) {
+ options[spin_id] = Ext.getCmp(spin_id).getValue();
+ options[spin_id + '_toggle'] = Ext.getCmp(
+ spin_id + '_toggle'
+ ).getValue();
+ });
+ this.spin_int_ids.forEach(function(spin_int_id) {
+ options[spin_int_id] = Ext.getCmp(spin_int_id).getValue();
+ options[spin_int_id + '_toggle'] = Ext.getCmp(
+ spin_int_id + '_toggle'
+ ).getValue();
+ });
+ this.chk_ids.forEach(function(chk_id) {
+ options[chk_id] = Ext.getCmp(chk_id).getValue();
+ options[chk_id + '_toggle'] = Ext.getCmp(
+ chk_id + '_toggle'
+ ).getValue();
+ });
+
+ if (
+ options['copy_torrent_toggle'] &&
+ options['path'] === options['copy_torrent']
+ ) {
+ throw _(
+ '"Watch Folder" directory and "Copy of .torrent' +
+ ' files to" directory cannot be the same!'
+ );
+ }
+
+ return options;
+ },
+
+ loadOptions: function(options) {
+ /*
+ * Populate all available options data to the UI
+ */
+ var value;
+
+ if (options === undefined) {
+ options = {};
+ }
+ Ext.getCmp('enabled').setValue(
+ options['enabled'] !== undefined ? options['enabled'] : true
+ );
+ Ext.getCmp('isnt_append_extension').setValue(true);
+ Ext.getCmp('append_extension_toggle').setValue(
+ options['append_extension_toggle'] !== undefined
+ ? options['append_extension_toggle']
+ : false
+ );
+ Ext.getCmp('append_extension').setValue(
+ options['append_extension'] !== undefined
+ ? options['append_extension']
+ : '.added'
+ );
+ Ext.getCmp('download_location_toggle').setValue(
+ options['download_location_toggle'] !== undefined
+ ? options['download_location_toggle']
+ : false
+ );
+ Ext.getCmp('copy_torrent_toggle').setValue(
+ options['copy_torrent_toggle'] !== undefined
+ ? options['copy_torrent_toggle']
+ : false
+ );
+ Ext.getCmp('delete_copy_torrent_toggle').setValue(
+ options['delete_copy_torrent_toggle'] !== undefined
+ ? options['delete_copy_torrent_toggle']
+ : false
+ );
+
+ value =
+ options['seed_mode'] !== undefined ? options['seed_mode'] : false;
+ Ext.getCmp('seed_mode').setValue(value);
+
+ this.accounts.removeAll(true);
+ this.labels.removeAll(true);
+ Ext.getCmp('owner').store = this.accounts;
+ Ext.getCmp('label').store = this.labels;
+ Ext.getCmp('label').setValue(
+ options['label'] !== undefined ? options['label'] : ''
+ );
+ Ext.getCmp('label_toggle').setValue(
+ options['label_toggle'] !== undefined
+ ? options['label_toggle']
+ : false
+ );
+
+ this.spin_ids.forEach(function(spin_id) {
+ Ext.getCmp(spin_id).setValue(
+ options[spin_id] !== undefined ? options[spin_id] : 0
+ );
+ Ext.getCmp(spin_id + '_toggle').setValue(
+ options[spin_id + '_toggle'] !== undefined
+ ? options[spin_id + '_toggle']
+ : false
+ );
+ });
+ this.chk_ids.forEach(function(chk_id) {
+ Ext.getCmp(chk_id).setValue(
+ options[chk_id] !== undefined ? options[chk_id] : true
+ );
+ Ext.getCmp(chk_id + '_toggle').setValue(
+ options[chk_id + '_toggle'] !== undefined
+ ? options[chk_id + '_toggle']
+ : false
+ );
+ });
+ value =
+ options['add_paused'] !== undefined ? options['add_paused'] : true;
+ if (!value) {
+ Ext.getCmp('not_add_paused').setValue(true);
+ }
+ value =
+ options['queue_to_top'] !== undefined
+ ? options['queue_to_top']
+ : true;
+ if (!value) {
+ Ext.getCmp('not_queue_to_top').setValue(true);
+ }
+ value =
+ options['auto_managed'] !== undefined
+ ? options['auto_managed']
+ : true;
+ if (!value) {
+ Ext.getCmp('not_auto_managed').setValue(true);
+ }
+ [
+ 'move_completed_path',
+ 'path',
+ 'download_location',
+ 'copy_torrent',
+ ].forEach(function(field) {
+ value = options[field] !== undefined ? options[field] : '';
+ Ext.getCmp(field).setValue(value);
+ });
+
+ if (Object.keys(options).length === 0) {
+ deluge.client.core.get_config({
+ success: function(config) {
+ var value;
+ Ext.getCmp('download_location').setValue(
+ options['download_location'] !== undefined
+ ? options['download_location']
+ : config['download_location']
+ );
+ value =
+ options['move_completed_toggle'] !== undefined
+ ? options['move_completed_toggle']
+ : config['move_completed'];
+ if (value) {
+ Ext.getCmp('move_completed_toggle').setValue(
+ options['move_completed_toggle'] !== undefined
+ ? options['move_completed_toggle']
+ : false
+ );
+ Ext.getCmp('move_completed_path').setValue(
+ options['move_completed_path'] !== undefined
+ ? options['move_completed_path']
+ : config['move_completed_path']
+ );
+ }
+ value =
+ options['copy_torrent_toggle'] !== undefined
+ ? options['copy_torrent_toggle']
+ : config['copy_torrent_file'];
+ if (value) {
+ Ext.getCmp('copy_torrent_toggle').setValue(true);
+ Ext.getCmp('copy_torrent').setValue(
+ options['copy_torrent'] !== undefined
+ ? options['copy_torrent']
+ : config['torrentfiles_location']
+ );
+ }
+ value =
+ options['delete_copy_torrent_toggle'] !== undefined
+ ? options['copy_torrent_toggle']
+ : config['del_copy_torrent_file'];
+ if (value) {
+ Ext.getCmp('delete_copy_torrent_toggle').setValue(true);
+ }
+ },
+ });
+ }
+
+ deluge.client.core.get_enabled_plugins({
+ success: function(plugins) {
+ if (plugins !== undefined && plugins.indexOf('Label') > -1) {
+ this.MainTab.LabelFset.setVisible(true);
+ deluge.client.label.get_labels({
+ success: function(labels) {
+ for (
+ var index = 0;
+ index < labels.length;
+ index++
+ ) {
+ labels[index] = [labels[index]];
+ }
+ this.labels.loadData(labels, false);
+ },
+ failure: function(failure) {
+ console.error(failure);
+ },
+ scope: this,
+ });
+ } else {
+ this.MainTab.LabelFset.setVisible(false);
+ }
+ },
+ scope: this,
+ });
+
+ var me = this;
+
+ function on_accounts(accounts, owner) {
+ for (var index = 0; index < accounts.length; index++) {
+ accounts[index] = [accounts[index]['username']];
+ }
+ me.accounts.loadData(accounts, false);
+ Ext.getCmp('owner')
+ .setValue(owner)
+ .enable();
+ }
+
+ function on_accounts_failure(failure) {
+ deluge.client.autoadd.get_auth_user({
+ success: function(user) {
+ me.accounts.loadData([[user]], false);
+ Ext.getCmp('owner')
+ .setValue(user)
+ .disable(true);
+ },
+ scope: this,
+ });
+ }
+
+ deluge.client.autoadd.is_admin_level({
+ success: function(is_admin) {
+ if (is_admin) {
+ deluge.client.core.get_known_accounts({
+ success: function(accounts) {
+ deluge.client.autoadd.get_auth_user({
+ success: function(user) {
+ on_accounts(
+ accounts,
+ options['owner'] !== undefined
+ ? options['owner']
+ : user
+ );
+ },
+ scope: this,
+ });
+ },
+ failure: on_accounts_failure,
+ scope: this,
+ });
+ } else {
+ on_accounts_failure(null);
+ }
+ },
+ scope: this,
+ });
+ },
+});
+
+/**
+ * @class Deluge.ux.AutoAdd.EditAutoAddCommandWindow
+ * @extends Deluge.ux.AutoAdd.AutoAddWindowBase
+ */
+Deluge.ux.AutoAdd.EditAutoAddCommandWindow = Ext.extend(
+ Deluge.ux.AutoAdd.AutoAddWindowBase,
+ {
+ title: _('Edit Watch Folder'),
+
+ initComponent: function() {
+ Deluge.ux.AutoAdd.EditAutoAddCommandWindow.superclass.initComponent.call(
+ this
+ );
+ this.addButton(_('Save'), this.onSaveClick, this);
+ this.addEvents({
+ watchdiredit: true,
+ });
+ },
+
+ show: function(watchdir_id, options) {
+ Deluge.ux.AutoAdd.EditAutoAddCommandWindow.superclass.show.call(
+ this
+ );
+ this.watchdir_id = watchdir_id;
+ this.loadOptions(options);
+ },
+
+ onSaveClick: function() {
+ try {
+ var options = this.getOptions();
+ deluge.client.autoadd.set_options(this.watchdir_id, options, {
+ success: function() {
+ this.fireEvent('watchdiredit', this, options);
+ },
+ scope: this,
+ });
+ } catch (err) {
+ Ext.Msg.show({
+ title: _('Incompatible Option'),
+ msg: err,
+ buttons: Ext.Msg.OK,
+ scope: this,
+ });
+ }
+
+ this.hide();
+ },
+ }
+);
+
+/**
+ * @class Deluge.ux.AutoAdd.AddAutoAddCommandWindow
+ * @extends Deluge.ux.AutoAdd.AutoAddWindowBase
+ */
+Deluge.ux.AutoAdd.AddAutoAddCommandWindow = Ext.extend(
+ Deluge.ux.AutoAdd.AutoAddWindowBase,
+ {
+ title: _('Add Watch Folder'),
+
+ initComponent: function() {
+ Deluge.ux.AutoAdd.AddAutoAddCommandWindow.superclass.initComponent.call(
+ this
+ );
+ this.addButton(_('Add'), this.onAddClick, this);
+ this.addEvents({
+ watchdiradd: true,
+ });
+ },
+
+ show: function() {
+ Deluge.ux.AutoAdd.AddAutoAddCommandWindow.superclass.show.call(
+ this
+ );
+ this.loadOptions();
+ },
+
+ onAddClick: function() {
+ var options = this.getOptions();
+ deluge.client.autoadd.add(options, {
+ success: function() {
+ this.fireEvent('watchdiradd', this, options);
+ this.hide();
+ },
+ failure: function(err) {
+ const regex = /: (.*\n)\n?\]/m;
+ var error;
+ if ((error = regex.exec(err.error.message)) !== null) {
+ error = error[1];
+ } else {
+ error = err.error.message;
+ }
+ Ext.Msg.show({
+ title: _('Incompatible Option'),
+ msg: error,
+ buttons: Ext.Msg.OK,
+ scope: this,
+ });
+ },
+ scope: this,
+ });
+ },
+ }
+);
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui
new file mode 100644
index 0000000..a4cd364
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui
@@ -0,0 +1,1342 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">-1</property>
+ <property name="upper">10000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="lower">-1</property>
+ <property name="upper">10000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="lower">-1</property>
+ <property name="upper">10000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment4">
+ <property name="lower">-1</property>
+ <property name="upper">10000</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment5">
+ <property name="upper">100</property>
+ <property name="value">2</property>
+ <property name="step_increment">0.10000000149</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="options_dialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Watch Folder Properties</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <signal name="close" handler="on_options_dialog_close" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="opts_cancel_button">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_opts_cancel" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="opts_add_button">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_opts_add" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="opts_apply_button">
+ <property name="label">gtk-apply</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_opts_apply" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="path_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">If a .torrent file is added to this directory,
+it will be added to the session.</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="path_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">If a .torrent file is added to this directory,
+it will be added to the session.</property>
+ <property name="action">select-folder</property>
+ <property name="title" translatable="yes">Select A Folder</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="enabled">
+ <property name="label" translatable="yes">Enable this watch folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Watch Folder&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="isnt_append_extension">
+ <property name="label" translatable="yes">Delete .torrent after adding</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Once the torrent is added to the session,
+the .torrent will be deleted.</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkRadioButton" id="append_extension_toggle">
+ <property name="label" translatable="yes">Append extension after adding:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Once the torrent is added to the session,
+an extension will be appended to the .torrent
+and it will remain in the same directory.</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">isnt_append_extension</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="append_extension">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="text" translatable="yes">.added</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkRadioButton" id="copy_torrent_toggle">
+ <property name="label" translatable="yes">Copy of .torrent files to:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Once the torrent is added to the session,
+the .torrent will copied to the chosen directory
+and deleted from the watch folder.</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">isnt_append_extension</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="copy_torrent_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="copy_torrent_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action">select-folder</property>
+ <property name="title" translatable="yes">Select A Folder</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="delete_copy_torrent_toggle">
+ <property name="label" translatable="yes">Delete copy of torrent file on remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Once the torrent is deleted from the session,
+also delete the .torrent file used to add it.</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_padding">15</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Torrent File Action&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="download_location_toggle">
+ <property name="label" translatable="yes">Set download folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">This folder will be where the torrent data is downloaded to.</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="download_location_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="download_location_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action">select-folder</property>
+ <property name="title" translatable="yes">Select A Folder</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Download Folder&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="move_completed_toggle">
+ <property name="label" translatable="yes">Set move completed folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="move_completed_path_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="move_completed_path_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action">select-folder</property>
+ <property name="title" translatable="yes">Select A Folder</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="move_completed">
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Move Completed&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="label_frame">
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="label_toggle">
+ <property name="label" translatable="yes">Label: </property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_entry">True</property>
+ <child internal-child="entry">
+ <object class="GtkEntry" id="combobox-entry1">
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Label&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Main</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="spacing">5</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="OwnerFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkComboBox" id="OwnerCombobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">The user selected here will be the owner of the torrent.</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Owner&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">3</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">2</property>
+ <property name="row_spacing">4</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="max_upload_speed_toggle">
+ <property name="label" translatable="yes">Max Upload Speed:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="max_connections_toggle">
+ <property name="label" translatable="yes">Max Connections:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="max_upload_slots_toggle">
+ <property name="label" translatable="yes">Max Upload Slots:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_download_speed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_upload_speed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_connections">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment3</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_upload_slots">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment4</property>
+ <property name="climb_rate">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="max_download_speed_toggle">
+ <property name="label" translatable="yes">Max Download Speed:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Bandwidth&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">2</property>
+ <property name="row_spacing">4</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="stop_at_ratio_toggle">
+ <property name="label" translatable="yes">Stop seed at ratio:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkCheckButton" id="remove_at_ratio">
+ <property name="label" translatable="yes">Remove at ratio</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="auto_managed_toggle">
+ <property name="label" translatable="yes">Auto Managed:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="remove_at_ratio_toggle">
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="stop_ratio_toggle">
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="stop_ratio">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment5</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="auto_managed_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkRadioButton" id="auto_managed">
+ <property name="label">gtk-yes</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="isnt_auto_managed">
+ <property name="label">gtk-no</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">auto_managed</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="stop_at_ratio">
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="add_paused_toggle">
+ <property name="label" translatable="yes">Add Paused:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="add_paused_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkRadioButton" id="add_paused">
+ <property name="label">gtk-yes</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="isnt_add_paused">
+ <property name="label">gtk-no</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">add_paused</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="queue_to_top_toggle">
+ <property name="label" translatable="yes">Queue to:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkRadioButton" id="queue_to_top">
+ <property name="label" translatable="yes">Top</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="isnt_queue_to_top">
+ <property name="label" translatable="yes">Bottom</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">queue_to_top</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="seed_mode">
+ <property name="label" translatable="yes">Skip File Hash Check</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Queue&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Options</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">opts_cancel_button</action-widget>
+ <action-widget response="0">opts_add_button</action-widget>
+ <action-widget response="0">opts_apply_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/main_tab.js b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/main_tab.js
new file mode 100644
index 0000000..79d2600
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/main_tab.js
@@ -0,0 +1,304 @@
+/**
+ * Script: main_tab.js
+ * The client-side javascript code for the AutoAdd plugin.
+ *
+ * Copyright (C) 2009 GazpachoKing <chase.sterling@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.
+ */
+
+Ext.ns('Deluge.ux.AutoAdd');
+
+/**
+ * @class Deluge.ux.AutoAdd.AutoAddMainPanel
+ * @extends Ext.Panel
+ */
+Deluge.ux.AutoAdd.AutoAddMainPanel = Ext.extend(Ext.Panel, {
+ id: 'main_tab_panel',
+ title: _('Main'),
+
+ initComponent: function() {
+ Deluge.ux.AutoAdd.AutoAddMainPanel.superclass.initComponent.call(this);
+ this.watchFolderFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Watch Folder'),
+ defaultType: 'textfield',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ width: '85%',
+ labelWidth: 1,
+ items: [
+ {
+ xtype: 'textfield',
+ id: 'path',
+ hideLabel: true,
+ width: 304,
+ },
+ {
+ hideLabel: true,
+ id: 'enabled',
+ xtype: 'checkbox',
+ boxLabel: _('Enable this watch folder'),
+ checked: true,
+ },
+ ],
+ });
+
+ this.torrentActionFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Torrent File Action'),
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ width: '85%',
+ labelWidth: 1,
+ defaults: {
+ style: 'margin-bottom: 2px',
+ },
+ items: [
+ {
+ xtype: 'radiogroup',
+ columns: 1,
+ items: [
+ {
+ xtype: 'radio',
+ name: 'torrent_action',
+ id: 'isnt_append_extension',
+ boxLabel: _('Delete .torrent after adding'),
+ checked: true,
+ hideLabel: true,
+ listeners: {
+ check: function(cb, newValue) {
+ if (newValue) {
+ Ext.getCmp(
+ 'append_extension'
+ ).setDisabled(newValue);
+ Ext.getCmp('copy_torrent').setDisabled(
+ newValue
+ );
+ Ext.getCmp(
+ 'delete_copy_torrent_toggle'
+ ).setDisabled(newValue);
+ }
+ },
+ },
+ },
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ items: [
+ {
+ xtype: 'radio',
+ name: 'torrent_action',
+ id: 'append_extension_toggle',
+ boxLabel: _(
+ 'Append extension after adding:'
+ ),
+ hideLabel: true,
+ listeners: {
+ check: function(cb, newValue) {
+ if (newValue) {
+ Ext.getCmp(
+ 'append_extension'
+ ).setDisabled(!newValue);
+ Ext.getCmp(
+ 'copy_torrent'
+ ).setDisabled(newValue);
+ Ext.getCmp(
+ 'delete_copy_torrent_toggle'
+ ).setDisabled(newValue);
+ }
+ },
+ },
+ },
+ {
+ xtype: 'textfield',
+ id: 'append_extension',
+ hideLabel: true,
+ disabled: true,
+ style: 'margin-left: 2px',
+ width: 112,
+ },
+ ],
+ },
+ {
+ xtype: 'container',
+ hideLabel: true,
+ items: [
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ items: [
+ {
+ xtype: 'radio',
+ name: 'torrent_action',
+ id: 'copy_torrent_toggle',
+ boxLabel: _(
+ 'Copy of .torrent files to:'
+ ),
+ hideLabel: true,
+ listeners: {
+ check: function(cb, newValue) {
+ if (newValue) {
+ Ext.getCmp(
+ 'append_extension'
+ ).setDisabled(newValue);
+ Ext.getCmp(
+ 'copy_torrent'
+ ).setDisabled(
+ !newValue
+ );
+ Ext.getCmp(
+ 'delete_copy_torrent_toggle'
+ ).setDisabled(
+ !newValue
+ );
+ }
+ },
+ },
+ },
+ {
+ xtype: 'textfield',
+ id: 'copy_torrent',
+ hideLabel: true,
+ disabled: true,
+ style: 'margin-left: 2px',
+ width: 152,
+ },
+ ],
+ },
+ {
+ xtype: 'checkbox',
+ id: 'delete_copy_torrent_toggle',
+ boxLabel: _(
+ 'Delete copy of torrent file on remove'
+ ),
+ style: 'margin-left: 10px',
+ disabled: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+
+ this.downloadFolderFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Download Folder'),
+ defaultType: 'textfield',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ width: '85%',
+ labelWidth: 1,
+ items: [
+ {
+ hideLabel: true,
+ id: 'download_location_toggle',
+ xtype: 'checkbox',
+ boxLabel: _('Set download folder'),
+ listeners: {
+ check: function(cb, checked) {
+ Ext.getCmp('download_location').setDisabled(
+ !checked
+ );
+ },
+ },
+ },
+ {
+ xtype: 'textfield',
+ id: 'download_location',
+ hideLabel: true,
+ width: 304,
+ disabled: true,
+ },
+ ],
+ });
+
+ this.moveCompletedFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Move Completed'),
+ defaultType: 'textfield',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ width: '85%',
+ labelWidth: 1,
+ items: [
+ {
+ hideLabel: true,
+ id: 'move_completed_toggle',
+ xtype: 'checkbox',
+ boxLabel: _('Set move completed folder'),
+ listeners: {
+ check: function(cb, checked) {
+ Ext.getCmp('move_completed_path').setDisabled(
+ !checked
+ );
+ },
+ },
+ },
+ {
+ xtype: 'textfield',
+ id: 'move_completed_path',
+ hideLabel: true,
+ width: 304,
+ disabled: true,
+ },
+ ],
+ });
+
+ this.LabelFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Label'),
+ defaultType: 'textfield',
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 3px;',
+ //width: '85%',
+ labelWidth: 1,
+ //hidden: true,
+ items: [
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ items: [
+ {
+ hashLabel: false,
+ id: 'label_toggle',
+ xtype: 'checkbox',
+ boxLabel: _('Label:'),
+ listeners: {
+ check: function(cb, checked) {
+ Ext.getCmp('label').setDisabled(!checked);
+ },
+ },
+ },
+ {
+ xtype: 'combo',
+ id: 'label',
+ hideLabel: true,
+ //width: 220,
+ width: 254,
+ disabled: true,
+ style: 'margin-left: 2px',
+ mode: 'local',
+ valueField: 'displayText',
+ displayField: 'displayText',
+ },
+ ],
+ },
+ ],
+ });
+
+ this.add([
+ this.watchFolderFset,
+ this.torrentActionFset,
+ this.downloadFolderFset,
+ this.moveCompletedFset,
+ this.LabelFset,
+ ]);
+ },
+});
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/options_tab.js b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/options_tab.js
new file mode 100644
index 0000000..a69490c
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options/options_tab.js
@@ -0,0 +1,302 @@
+/**
+ * Script: options_tab.js
+ * The client-side javascript code for the AutoAdd plugin.
+ *
+ * Copyright (C) 2009 GazpachoKing <chase.sterling@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.
+ */
+
+Ext.ns('Deluge.ux.AutoAdd');
+
+/**
+ * @class Deluge.ux.AutoAdd.AutoAddOptionsPanel
+ * @extends Ext.Panel
+ */
+Deluge.ux.AutoAdd.AutoAddOptionsPanel = Ext.extend(Ext.Panel, {
+ id: 'options_tab_panel',
+ title: _('Options'),
+
+ initComponent: function() {
+ Deluge.ux.AutoAdd.AutoAddOptionsPanel.superclass.initComponent.call(
+ this
+ );
+ var maxDownload = {
+ idCheckbox: 'max_download_speed_toggle',
+ labelCheckbox: 'Max Download Speed (KiB/s):',
+ idSpinner: 'max_download_speed',
+ decimalPrecision: 1,
+ };
+ var maxUploadSpeed = {
+ idCheckbox: 'max_upload_speed_toggle',
+ labelCheckbox: 'Max upload Speed (KiB/s):',
+ idSpinner: 'max_upload_speed',
+ decimalPrecision: 1,
+ };
+ var maxConnections = {
+ idCheckbox: 'max_connections_toggle',
+ labelCheckbox: 'Max Connections::',
+ idSpinner: 'max_connections',
+ decimalPrecision: 0,
+ };
+ var maxUploadSlots = {
+ idCheckbox: 'max_upload_slots_toggle',
+ labelCheckbox: 'Max Upload Slots:',
+ idSpinner: 'max_upload_slots',
+ decimalPrecision: 0,
+ };
+ // queue data
+ var addPause = {
+ idCheckbox: 'add_paused_toggle',
+ labelCheckbox: 'Add Pause:',
+ nameRadio: 'add_paused',
+ labelRadio: {
+ yes: 'Yes',
+ no: 'No',
+ },
+ };
+ var queueTo = {
+ idCheckbox: 'queue_to_top_toggle',
+ labelCheckbox: 'Queue To:',
+ nameRadio: 'queue_to_top',
+ labelRadio: {
+ yes: 'Top',
+ no: 'Bottom',
+ },
+ };
+ var autoManaged = {
+ idCheckbox: 'auto_managed_toggle',
+ labelCheckbox: 'Auto Managed:',
+ nameRadio: 'auto_managed',
+ labelRadio: {
+ yes: 'Yes',
+ no: 'No',
+ },
+ };
+
+ this.ownerFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Owner'),
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ //width: '85%',
+ labelWidth: 1,
+ items: [
+ {
+ xtype: 'combo',
+ id: 'owner',
+ hideLabel: true,
+ width: 312,
+ mode: 'local',
+ valueField: 'displayText',
+ displayField: 'displayText',
+ },
+ ],
+ });
+
+ this.bandwidthFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Bandwidth'),
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ //width: '85%',
+ labelWidth: 1,
+ defaults: {
+ style: 'margin-bottom: 5px',
+ },
+ });
+ this.bandwidthFset.add(this._getBandwidthContainer(maxDownload));
+ this.bandwidthFset.add(this._getBandwidthContainer(maxUploadSpeed));
+ this.bandwidthFset.add(this._getBandwidthContainer(maxConnections));
+ this.bandwidthFset.add(this._getBandwidthContainer(maxUploadSlots));
+
+ this.queueFset = new Ext.form.FieldSet({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Queue'),
+ style: 'margin-top: 3px; margin-bottom: 0px; padding-bottom: 0px;',
+ //width: '85%',
+ labelWidth: 1,
+ defaults: {
+ style: 'margin-bottom: 5px',
+ },
+ items: [
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ },
+ ],
+ });
+ this.queueFset.add(this._getQueueContainer(addPause));
+ this.queueFset.add(this._getQueueContainer(queueTo));
+ this.queueFset.add(this._getQueueContainer(autoManaged));
+ this.queueFset.add({
+ xtype: 'container',
+ hideLabel: true,
+ items: [
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ items: [
+ {
+ xtype: 'checkbox',
+ id: 'stop_at_ratio_toggle',
+ boxLabel: _('Stop seed at ratio:'),
+ hideLabel: true,
+ width: 175,
+ listeners: {
+ check: function(cb, checked) {
+ Ext.getCmp('stop_ratio').setDisabled(
+ !checked
+ );
+ Ext.getCmp('remove_at_ratio').setDisabled(
+ !checked
+ );
+ },
+ },
+ },
+ {
+ xtype: 'spinnerfield',
+ id: 'stop_ratio',
+ hideLabel: true,
+ disabled: true,
+ value: 0.0,
+ minValue: 0.0,
+ maxValue: 100.0,
+ decimalPrecision: 1,
+ incrementValue: 0.1,
+ style: 'margin-left: 2px',
+ width: 100,
+ },
+ ],
+ },
+ {
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ style: 'margin-left: 10px',
+ items: [
+ {
+ xtype: 'checkbox',
+ id: 'remove_at_ratio',
+ boxLabel: _('Remove at ratio'),
+ disabled: true,
+ checked: true,
+ },
+ {
+ xtype: 'checkbox',
+ id: 'remove_at_ratio_toggle',
+ disabled: true,
+ checked: true,
+ hidden: true,
+ },
+ {
+ xtype: 'checkbox',
+ id: 'stop_ratio_toggle',
+ disabled: true,
+ checked: true,
+ hidden: true,
+ },
+ {
+ xtype: 'checkbox',
+ id: 'stop_ratio_toggle',
+ disabled: true,
+ checked: true,
+ hidden: true,
+ },
+ ],
+ },
+ ],
+ });
+ this.queueFset.add({
+ xtype: 'checkbox',
+ id: 'seed_mode',
+ boxLabel: _('Skip File Hash Check'),
+ hideLabel: true,
+ width: 175,
+ });
+
+ this.add([this.ownerFset, this.bandwidthFset, this.queueFset]);
+ },
+
+ _getBandwidthContainer: function(values) {
+ return new Ext.Container({
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ items: [
+ {
+ xtype: 'checkbox',
+ hideLabel: true,
+ id: values.idCheckbox,
+ boxLabel: _(values.labelCheckbox),
+ width: 175,
+ listeners: {
+ check: function(cb, checked) {
+ Ext.getCmp(values.idSpinner).setDisabled(!checked);
+ },
+ },
+ },
+ {
+ xtype: 'spinnerfield',
+ id: values.idSpinner,
+ hideLabel: true,
+ disabled: true,
+ minValue: -1,
+ maxValue: 10000,
+ value: 0.0,
+ decimalPrecision: values.decimalPrecision,
+ style: 'margin-left: 2px',
+ width: 100,
+ },
+ ],
+ });
+ },
+
+ _getQueueContainer: function(values) {
+ return new Ext.Container({
+ xtype: 'container',
+ layout: 'hbox',
+ hideLabel: true,
+ items: [
+ {
+ xtype: 'checkbox',
+ hideLabel: true,
+ id: values.idCheckbox,
+ boxLabel: _(values.labelCheckbox),
+ width: 175,
+ listeners: {
+ check: function(cb, checked) {
+ Ext.getCmp(values.nameRadio).setDisabled(!checked);
+ Ext.getCmp('not_' + values.nameRadio).setDisabled(
+ !checked
+ );
+ },
+ },
+ },
+ {
+ xtype: 'radio',
+ name: values.nameRadio,
+ id: values.nameRadio,
+ boxLabel: _(values.labelRadio.yes),
+ hideLabel: true,
+ checked: true,
+ disabled: true,
+ width: 50,
+ },
+ {
+ xtype: 'radio',
+ name: values.nameRadio,
+ id: 'not_' + values.nameRadio,
+ boxLabel: _(values.labelRadio.no),
+ hideLabel: true,
+ disabled: true,
+ },
+ ],
+ });
+ },
+});
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/config.ui b/deluge/plugins/AutoAdd/deluge_autoadd/data/config.ui
new file mode 100644
index 0000000..0e645d3
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/config.ui
@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkWindow" id="prefs_window">
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="prefs_box_1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="prefs_box">
+ <property name="width_request">340</property>
+ <property name="height_request">390</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">3</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="watchdirs_vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Watch Folders:&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="add_button">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_add_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="remove_button">
+ <property name="label">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_remove_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="edit_button">
+ <property name="label">gtk-edit</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_edit_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py b/deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py
new file mode 100644
index 0000000..16f0f7a
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py
@@ -0,0 +1,580 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2009 Damien Churchill <damoxc@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 logging
+import os
+
+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.common
+import deluge.component as component
+from deluge.plugins.pluginbase import Gtk3PluginBase
+from deluge.ui.client import client
+from deluge.ui.gtk3 import dialogs
+
+# isort:imports-localfolder
+from .common import get_resource
+
+log = logging.getLogger(__name__)
+
+
+class IncompatibleOption(Exception):
+ pass
+
+
+class OptionsDialog(object):
+ 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',
+ 'queue_to_top',
+ ]
+
+ def __init__(self):
+ self.accounts = Gtk.ListStore(str)
+ self.labels = Gtk.ListStore(str)
+ self.core_config = {}
+
+ def show(self, options=None, watchdir_id=None):
+ if options is None:
+ options = {}
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(get_resource('autoadd_options.ui'))
+ self.builder.connect_signals(
+ {
+ '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_toggle_toggled': self.on_toggle_toggled,
+ }
+ )
+ self.dialog = self.builder.get_object('options_dialog')
+ self.dialog.set_transient_for(component.get('Preferences').pref_dialog)
+
+ if watchdir_id:
+ # We have an existing watchdir_id, we are editing
+ self.builder.get_object('opts_add_button').hide()
+ self.builder.get_object('opts_apply_button').show()
+ self.watchdir_id = watchdir_id
+ else:
+ # We don't have an id, adding
+ self.builder.get_object('opts_add_button').show()
+ self.builder.get_object('opts_apply_button').hide()
+ self.watchdir_id = None
+
+ self.load_options(options)
+ self.dialog.run()
+
+ def load_options(self, options):
+ self.builder.get_object('enabled').set_active(options.get('enabled', True))
+ self.builder.get_object('append_extension_toggle').set_active(
+ options.get('append_extension_toggle', False)
+ )
+ self.builder.get_object('append_extension').set_text(
+ options.get('append_extension', '.added')
+ )
+ self.builder.get_object('download_location_toggle').set_active(
+ options.get('download_location_toggle', False)
+ )
+ self.builder.get_object('copy_torrent_toggle').set_active(
+ options.get('copy_torrent_toggle', False)
+ )
+ self.builder.get_object('delete_copy_torrent_toggle').set_active(
+ options.get('delete_copy_torrent_toggle', False)
+ )
+ self.builder.get_object('seed_mode').set_active(options.get('seed_mode', False))
+ self.accounts.clear()
+ self.labels.clear()
+ combobox = self.builder.get_object('OwnerCombobox')
+ combobox_render = Gtk.CellRendererText()
+ combobox.pack_start(combobox_render, True)
+ combobox.add_attribute(combobox_render, 'text', 0)
+ combobox.set_model(self.accounts)
+
+ label_widget = self.builder.get_object('label')
+ label_widget.get_child().set_text(options.get('label', ''))
+ label_widget.set_model(self.labels)
+ label_widget.set_entry_text_column(0)
+ self.builder.get_object('label_toggle').set_active(
+ options.get('label_toggle', False)
+ )
+
+ for spin_id in self.spin_ids + self.spin_int_ids:
+ self.builder.get_object(spin_id).set_value(options.get(spin_id, 0))
+ self.builder.get_object(spin_id + '_toggle').set_active(
+ options.get(spin_id + '_toggle', False)
+ )
+ for chk_id in self.chk_ids:
+ self.builder.get_object(chk_id).set_active(bool(options.get(chk_id, True)))
+ self.builder.get_object(chk_id + '_toggle').set_active(
+ options.get(chk_id + '_toggle', False)
+ )
+ if not options.get('add_paused', True):
+ self.builder.get_object('isnt_add_paused').set_active(True)
+ if not options.get('queue_to_top', True):
+ self.builder.get_object('isnt_queue_to_top').set_active(True)
+ if not options.get('auto_managed', True):
+ self.builder.get_object('isnt_auto_managed').set_active(True)
+ for field in [
+ 'move_completed_path',
+ 'path',
+ 'download_location',
+ 'copy_torrent',
+ ]:
+ if client.is_localhost():
+ self.builder.get_object(field + '_chooser').set_current_folder(
+ options.get(field, os.path.expanduser('~'))
+ )
+ self.builder.get_object(field + '_chooser').show()
+ self.builder.get_object(field + '_entry').hide()
+ else:
+ self.builder.get_object(field + '_entry').set_text(
+ options.get(field, '')
+ )
+ self.builder.get_object(field + '_entry').show()
+ self.builder.get_object(field + '_chooser').hide()
+ self.set_sensitive()
+
+ def on_core_config(config):
+ if client.is_localhost():
+ self.builder.get_object('download_location_chooser').set_current_folder(
+ options.get('download_location', config['download_location'])
+ )
+ if options.get('move_completed_toggle', config['move_completed']):
+ self.builder.get_object('move_completed_toggle').set_active(True)
+ self.builder.get_object(
+ 'move_completed_path_chooser'
+ ).set_current_folder(
+ options.get(
+ 'move_completed_path', config['move_completed_path']
+ )
+ )
+ if options.get('copy_torrent_toggle', config['copy_torrent_file']):
+ self.builder.get_object('copy_torrent_toggle').set_active(True)
+ self.builder.get_object('copy_torrent_chooser').set_current_folder(
+ options.get('copy_torrent', config['torrentfiles_location'])
+ )
+ else:
+ self.builder.get_object('download_location_entry').set_text(
+ options.get('download_location', config['download_location'])
+ )
+ if options.get('move_completed_toggle', config['move_completed']):
+ self.builder.get_object('move_completed_toggle').set_active(
+ options.get('move_completed_toggle', False)
+ )
+ self.builder.get_object('move_completed_path_entry').set_text(
+ options.get(
+ 'move_completed_path', config['move_completed_path']
+ )
+ )
+ if options.get('copy_torrent_toggle', config['copy_torrent_file']):
+ self.builder.get_object('copy_torrent_toggle').set_active(True)
+ self.builder.get_object('copy_torrent_entry').set_text(
+ options.get('copy_torrent', config['torrentfiles_location'])
+ )
+
+ if options.get(
+ 'delete_copy_torrent_toggle', config['del_copy_torrent_file']
+ ):
+ self.builder.get_object('delete_copy_torrent_toggle').set_active(True)
+
+ if not options:
+ client.core.get_config().addCallback(on_core_config)
+
+ def on_accounts(accounts, owner):
+ log.debug('Got Accounts')
+ selected_iter = None
+ for account in accounts:
+ acc_iter = self.accounts.append()
+ self.accounts.set_value(acc_iter, 0, account['username'])
+ if account['username'] == owner:
+ selected_iter = acc_iter
+ self.builder.get_object('OwnerCombobox').set_active_iter(selected_iter)
+
+ def on_accounts_failure(failure):
+ log.debug('Failed to get accounts!!! %s', failure)
+ acc_iter = self.accounts.append()
+ self.accounts.set_value(acc_iter, 0, client.get_auth_user())
+ self.builder.get_object('OwnerCombobox').set_active(0)
+ self.builder.get_object('OwnerCombobox').set_sensitive(False)
+
+ def on_labels(labels):
+ log.debug('Got Labels: %s', labels)
+ for label in labels:
+ self.labels.set_value(self.labels.append(), 0, label)
+ label_widget = self.builder.get_object('label')
+ label_widget.set_model(self.labels)
+ label_widget.set_entry_text_column(0)
+
+ def on_failure(failure):
+ log.exception(failure)
+
+ def on_get_enabled_plugins(result):
+ if 'Label' in result:
+ self.builder.get_object('label_frame').show()
+ client.label.get_labels().addCallback(on_labels).addErrback(on_failure)
+ else:
+ self.builder.get_object('label_frame').hide()
+ self.builder.get_object('label_toggle').set_active(False)
+
+ client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
+ if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
+ client.core.get_known_accounts().addCallback(
+ on_accounts, options.get('owner', client.get_auth_user())
+ ).addErrback(on_accounts_failure)
+ else:
+ acc_iter = self.accounts.append()
+ self.accounts.set_value(acc_iter, 0, client.get_auth_user())
+ self.builder.get_object('OwnerCombobox').set_active(0)
+ self.builder.get_object('OwnerCombobox').set_sensitive(False)
+
+ def set_sensitive(self):
+ maintoggles = [
+ 'download_location',
+ 'append_extension',
+ 'move_completed',
+ 'label',
+ 'max_download_speed',
+ 'max_upload_speed',
+ 'max_connections',
+ 'max_upload_slots',
+ 'add_paused',
+ 'auto_managed',
+ 'stop_at_ratio',
+ 'queue_to_top',
+ 'copy_torrent',
+ ]
+ for maintoggle in maintoggles:
+ self.on_toggle_toggled(self.builder.get_object(maintoggle + '_toggle'))
+
+ def on_toggle_toggled(self, tb):
+ toggle = tb.get_name().replace('_toggle', '')
+ isactive = tb.get_active()
+ if toggle == 'download_location':
+ self.builder.get_object('download_location_chooser').set_sensitive(isactive)
+ self.builder.get_object('download_location_entry').set_sensitive(isactive)
+ elif toggle == 'append_extension':
+ self.builder.get_object('append_extension').set_sensitive(isactive)
+ elif toggle == 'copy_torrent':
+ self.builder.get_object('copy_torrent_entry').set_sensitive(isactive)
+ self.builder.get_object('copy_torrent_chooser').set_sensitive(isactive)
+ self.builder.get_object('delete_copy_torrent_toggle').set_sensitive(
+ isactive
+ )
+ elif toggle == 'move_completed':
+ self.builder.get_object('move_completed_path_chooser').set_sensitive(
+ isactive
+ )
+ self.builder.get_object('move_completed_path_entry').set_sensitive(isactive)
+ self.builder.get_object('move_completed').set_active(isactive)
+ elif toggle == 'label':
+ self.builder.get_object('label').set_sensitive(isactive)
+ elif toggle == 'max_download_speed':
+ self.builder.get_object('max_download_speed').set_sensitive(isactive)
+ elif toggle == 'max_upload_speed':
+ self.builder.get_object('max_upload_speed').set_sensitive(isactive)
+ elif toggle == 'max_connections':
+ self.builder.get_object('max_connections').set_sensitive(isactive)
+ elif toggle == 'max_upload_slots':
+ self.builder.get_object('max_upload_slots').set_sensitive(isactive)
+ elif toggle == 'add_paused':
+ self.builder.get_object('add_paused').set_sensitive(isactive)
+ self.builder.get_object('isnt_add_paused').set_sensitive(isactive)
+ elif toggle == 'queue_to_top':
+ self.builder.get_object('queue_to_top').set_sensitive(isactive)
+ self.builder.get_object('isnt_queue_to_top').set_sensitive(isactive)
+ elif toggle == 'auto_managed':
+ self.builder.get_object('auto_managed').set_sensitive(isactive)
+ self.builder.get_object('isnt_auto_managed').set_sensitive(isactive)
+ elif toggle == 'stop_at_ratio':
+ self.builder.get_object('remove_at_ratio_toggle').set_active(isactive)
+ self.builder.get_object('stop_ratio_toggle').set_active(isactive)
+ self.builder.get_object('stop_at_ratio').set_active(isactive)
+ self.builder.get_object('stop_ratio').set_sensitive(isactive)
+ self.builder.get_object('remove_at_ratio').set_sensitive(isactive)
+
+ def on_apply(self, event=None):
+ try:
+ options = self.generate_opts()
+ client.autoadd.set_options(str(self.watchdir_id), options).addCallbacks(
+ self.on_added, self.on_error_show
+ )
+ except IncompatibleOption as ex:
+ dialogs.ErrorDialog(_('Incompatible Option'), str(ex), self.dialog).run()
+
+ def on_error_show(self, result):
+ d = dialogs.ErrorDialog(_('Error'), result.value.exception_msg, self.dialog)
+ result.cleanFailure()
+ d.run()
+
+ def on_added(self, result):
+ self.dialog.destroy()
+
+ def on_add(self, event=None):
+ try:
+ options = self.generate_opts()
+ client.autoadd.add(options).addCallbacks(self.on_added, self.on_error_show)
+ except IncompatibleOption as ex:
+ dialogs.ErrorDialog(_('Incompatible Option'), str(ex), self.dialog).run()
+
+ def on_cancel(self, event=None):
+ self.dialog.destroy()
+
+ def generate_opts(self):
+ # generate options dict based on gtk objects
+ options = {}
+ options['enabled'] = self.builder.get_object('enabled').get_active()
+ if client.is_localhost():
+ options['path'] = self.builder.get_object('path_chooser').get_filename()
+ options['download_location'] = self.builder.get_object(
+ 'download_location_chooser'
+ ).get_filename()
+ options['move_completed_path'] = self.builder.get_object(
+ 'move_completed_path_chooser'
+ ).get_filename()
+ options['copy_torrent'] = self.builder.get_object(
+ 'copy_torrent_chooser'
+ ).get_filename()
+ else:
+ options['path'] = self.builder.get_object('path_entry').get_text()
+ options['download_location'] = self.builder.get_object(
+ 'download_location_entry'
+ ).get_text()
+ options['move_completed_path'] = self.builder.get_object(
+ 'move_completed_path_entry'
+ ).get_text()
+ options['copy_torrent'] = self.builder.get_object(
+ 'copy_torrent_entry'
+ ).get_text()
+
+ options['label'] = (
+ self.builder.get_object('label').get_child().get_text().lower()
+ )
+ options['append_extension'] = self.builder.get_object(
+ 'append_extension'
+ ).get_text()
+ options['owner'] = self.accounts[
+ self.builder.get_object('OwnerCombobox').get_active()
+ ][0]
+
+ for key in [
+ 'append_extension_toggle',
+ 'download_location_toggle',
+ 'label_toggle',
+ 'copy_torrent_toggle',
+ 'delete_copy_torrent_toggle',
+ 'seed_mode',
+ ]:
+ options[key] = self.builder.get_object(key).get_active()
+
+ for spin_id in self.spin_ids:
+ options[spin_id] = self.builder.get_object(spin_id).get_value()
+ options[spin_id + '_toggle'] = self.builder.get_object(
+ spin_id + '_toggle'
+ ).get_active()
+ for spin_int_id in self.spin_int_ids:
+ options[spin_int_id] = self.builder.get_object(
+ spin_int_id
+ ).get_value_as_int()
+ options[spin_int_id + '_toggle'] = self.builder.get_object(
+ spin_int_id + '_toggle'
+ ).get_active()
+ for chk_id in self.chk_ids:
+ options[chk_id] = self.builder.get_object(chk_id).get_active()
+ options[chk_id + '_toggle'] = self.builder.get_object(
+ chk_id + '_toggle'
+ ).get_active()
+
+ if (
+ options['copy_torrent_toggle']
+ and options['path'] == options['copy_torrent']
+ ):
+ raise IncompatibleOption(
+ _(
+ '"Watch Folder" directory and "Copy of .torrent'
+ ' files to" directory cannot be the same!'
+ )
+ )
+ return options
+
+
+class GtkUI(Gtk3PluginBase):
+ def enable(self):
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(get_resource('config.ui'))
+ self.builder.connect_signals(self)
+ 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.builder.get_object('watchdirs_vbox')
+ sw = Gtk.ScrolledWindow()
+ sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
+ sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.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.builder.get_object('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, str)
+ for watchdir_id, watchdir in self.watchdirs.items():
+ store.append(
+ [
+ watchdir_id,
+ watchdir['enabled'],
+ watchdir.get('owner', 'localclient'),
+ watchdir['path'],
+ ]
+ )
+ return store
+
+ def create_columns(self, treeview):
+ renderer_toggle = Gtk.CellRendererToggle()
+ column = Gtk.TreeViewColumn(
+ _('Active'), renderer_toggle, activatable=1, 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, renderer_toggle)
+
+ renderertext = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn(_('Owner'), 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_has_tooltip(True)
+
+ renderertext = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn(_('Path'), renderertext, text=3)
+ column.set_sort_column_id(3)
+ treeview.append_column(column)
+ tt2 = Gtk.Tooltip()
+ tt2.set_text(_('Double-click to edit'))
+ 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() == _('Active'):
+ 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.builder.get_object('edit_button').set_sensitive(True)
+ self.builder.get_object('remove_button').set_sensitive(True)
+ else:
+ self.builder.get_object('edit_button').set_sensitive(False)
+ self.builder.get_object('remove_button').set_sensitive(False)
+
+ def on_apply_prefs(self):
+ log.debug('applying prefs for AutoAdd')
+ for watchdir_id, watchdir in self.watchdirs.items():
+ client.autoadd.set_options(watchdir_id, watchdir)
+
+ def on_show_prefs(self):
+ client.autoadd.get_watchdirs().addCallback(self.cb_get_config)
+
+ def on_options_changed_event(self):
+ client.autoadd.get_watchdirs().addCallback(self.cb_get_config)
+
+ def cb_get_config(self, watchdirs):
+ """callback for on show_prefs"""
+ log.trace('Got whatchdirs from core: %s', watchdirs)
+ self.watchdirs = watchdirs or {}
+ self.store.clear()
+ for watchdir_id, watchdir in self.watchdirs.items():
+ self.store.append(
+ [
+ watchdir_id,
+ watchdir['enabled'],
+ watchdir.get('owner', 'localclient'),
+ watchdir['path'],
+ ]
+ )
+ # Workaround for cached glade signal appearing when re-enabling plugin in same session
+ if self.builder.get_object('edit_button'):
+ # Disable the remove and edit buttons, because nothing in the store is selected
+ self.builder.get_object('remove_button').set_sensitive(False)
+ self.builder.get_object('edit_button').set_sensitive(False)
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/webui.py b/deluge/plugins/AutoAdd/deluge_autoadd/webui.py
new file mode 100644
index 0000000..7f36ba6
--- /dev/null
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/webui.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2009 Damien Churchill <damoxc@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 logging
+
+from deluge.plugins.pluginbase import WebPluginBase
+
+from .common import get_resource
+
+log = logging.getLogger(__name__)
+
+
+class WebUI(WebPluginBase):
+ scripts = [
+ get_resource('autoadd.js'),
+ get_resource('autoadd_options.js'),
+ get_resource('main_tab.js', True),
+ get_resource('options_tab.js', True),
+ ]
+
+ 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 0000000..fcd0183
--- /dev/null
+++ b/deluge/plugins/AutoAdd/setup.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
+# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2009 Damien Churchill <damoxc@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 setuptools import find_packages, setup
+
+__plugin_name__ = 'AutoAdd'
+__author__ = 'Chase Sterling, Pedro Algarvio'
+__author_email__ = 'chase.sterling@gmail.com, pedro@algarvio.me'
+__version__ = '1.8'
+__url__ = 'http://dev.deluge-torrent.org/wiki/Plugins/AutoAdd'
+__license__ = 'GPLv3'
+__description__ = 'Monitors folders for .torrent files.'
+__long_description__ = """"""
+__pkg_data__ = {'deluge_' + __plugin_name__.lower(): ['data/*', '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=find_packages(),
+ package_data=__pkg_data__,
+ entry_points="""
+ [deluge.plugin.core]
+ %s = deluge_%s:CorePlugin
+ [deluge.plugin.gtk3ui]
+ %s = deluge_%s:Gtk3UIPlugin
+ [deluge.plugin.web]
+ %s = deluge_%s:WebUIPlugin
+ """
+ % ((__plugin_name__, __plugin_name__.lower()) * 3),
+)