summaryrefslogtreecommitdiffstats
path: root/deluge/plugins/Label
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/plugins/Label')
-rw-r--r--deluge/plugins/Label/TODO11
-rw-r--r--deluge/plugins/Label/deluge_label/__init__.py40
-rw-r--r--deluge/plugins/Label/deluge_label/common.py23
-rw-r--r--deluge/plugins/Label/deluge_label/core.py351
-rw-r--r--deluge/plugins/Label/deluge_label/data/label.js642
-rw-r--r--deluge/plugins/Label/deluge_label/data/label_add.ui174
-rw-r--r--deluge/plugins/Label/deluge_label/data/label_options.ui735
-rw-r--r--deluge/plugins/Label/deluge_label/data/label_pref.ui56
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/__init__.py77
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/label_config.py61
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py263
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/submenu.py65
-rw-r--r--deluge/plugins/Label/deluge_label/test.py50
-rw-r--r--deluge/plugins/Label/deluge_label/webui.py27
-rw-r--r--deluge/plugins/Label/setup.py46
15 files changed, 2621 insertions, 0 deletions
diff --git a/deluge/plugins/Label/TODO b/deluge/plugins/Label/TODO
new file mode 100644
index 0000000..c6a5daa
--- /dev/null
+++ b/deluge/plugins/Label/TODO
@@ -0,0 +1,11 @@
+*grey bars are hard-coded , use theme to get bg-color.
+*label sub-menu is broken on 1'st popup.
+*replacing/restoring the sidebar model is a hack
+*config should save a label on bottom ok-button, not a seperate save-button per label
+*filters : add "Traffic" , use label-core for filtering ; needs hooks in torrentview.
+*torrentview: bugs/hacks in adding and removing columns
+*webui is functional but not polished.
+*move_torrent_to is not implemeted
+*no client-side validation (could be solved by a ui.aclient exception-plugin)
+*expand/arrows in sidebar are disabled to save space, fix the space issue or implement an alternative
+*fix and move tracker_host column+status-field to core.
diff --git a/deluge/plugins/Label/deluge_label/__init__.py b/deluge/plugins/Label/deluge_label/__init__.py
new file mode 100644
index 0000000..bc0b0f2
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+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 GtkUIPlugin(PluginInitBase):
+ def __init__(self, plugin_name):
+ from .gtkui import GtkUI as _pluginCls
+
+ self._plugin_cls = _pluginCls
+ super(GtkUIPlugin, 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/Label/deluge_label/common.py b/deluge/plugins/Label/deluge_label/common.py
new file mode 100644
index 0000000..4c9db09
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/common.py
@@ -0,0 +1,23 @@
+# -*- 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):
+ return resource_filename(__package__, os.path.join('data', filename))
diff --git a/deluge/plugins/Label/deluge_label/core.py b/deluge/plugins/Label/deluge_label/core.py
new file mode 100644
index 0000000..b16156c
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/core.py
@@ -0,0 +1,351 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+"""
+torrent-label core plugin.
+adds a status field for tracker.
+"""
+from __future__ import unicode_literals
+
+import logging
+import re
+
+import deluge.component as component
+from deluge.configmanager import ConfigManager
+from deluge.core.rpcserver import export
+from deluge.plugins.pluginbase import CorePluginBase
+
+log = logging.getLogger(__name__)
+
+RE_VALID = re.compile(r'[a-z0-9_\-\.]*\Z')
+
+KNOWN_STATES = ['Downloading', 'Seeding', 'Paused', 'Checking', 'Queued', 'Error']
+STATE = 'state'
+TRACKER = 'tracker'
+KEYWORD = 'keyword'
+LABEL = 'label'
+CONFIG_DEFAULTS = {
+ 'torrent_labels': {}, # torrent_id:label_id
+ 'labels': {}, # label_id:{name:value}
+}
+
+CORE_OPTIONS = ['auto_add_trackers']
+
+OPTIONS_DEFAULTS = {
+ 'apply_max': False,
+ 'max_download_speed': -1,
+ 'max_upload_speed': -1,
+ 'max_connections': -1,
+ 'max_upload_slots': -1,
+ 'prioritize_first_last': False,
+ 'apply_queue': False,
+ 'is_auto_managed': False,
+ 'stop_at_ratio': False,
+ 'stop_ratio': 2.0,
+ 'remove_at_ratio': False,
+ 'apply_move_completed': False,
+ 'move_completed': False,
+ 'move_completed_path': '',
+ 'auto_add': False,
+ 'auto_add_trackers': [],
+}
+
+NO_LABEL = 'No Label'
+
+
+def check_input(cond, message):
+ if not cond:
+ raise Exception(message)
+
+
+class Core(CorePluginBase):
+ """
+ self.labels = {label_id:label_options_dict}
+ self.torrent_labels = {torrent_id:label_id}
+ """
+
+ def enable(self):
+ log.info('*** Start Label plugin ***')
+ self.plugin = component.get('CorePluginManager')
+ self.plugin.register_status_field('label', self._status_get_label)
+
+ # __init__
+ core = component.get('Core')
+ self.config = ConfigManager('label.conf', defaults=CONFIG_DEFAULTS)
+ self.core_cfg = ConfigManager('core.conf')
+
+ # reduce typing, assigning some values to self...
+ self.torrents = core.torrentmanager.torrents
+ self.labels = self.config['labels']
+ self.torrent_labels = self.config['torrent_labels']
+
+ self.clean_initial_config()
+
+ component.get('EventManager').register_event_handler(
+ 'TorrentAddedEvent', self.post_torrent_add
+ )
+ component.get('EventManager').register_event_handler(
+ 'TorrentRemovedEvent', self.post_torrent_remove
+ )
+
+ # register tree:
+ component.get('FilterManager').register_tree_field(
+ 'label', self.init_filter_dict
+ )
+
+ log.debug('Label plugin enabled..')
+
+ def disable(self):
+ self.plugin.deregister_status_field('label')
+ component.get('FilterManager').deregister_tree_field('label')
+ component.get('EventManager').deregister_event_handler(
+ 'TorrentAddedEvent', self.post_torrent_add
+ )
+ component.get('EventManager').deregister_event_handler(
+ 'TorrentRemovedEvent', self.post_torrent_remove
+ )
+
+ def update(self):
+ pass
+
+ def init_filter_dict(self):
+ filter_dict = {label: 0 for label in self.labels}
+ filter_dict['All'] = len(self.torrents)
+ return filter_dict
+
+ # Plugin hooks #
+ def post_torrent_add(self, torrent_id, from_state):
+ if from_state:
+ return
+ log.debug('post_torrent_add')
+ torrent = self.torrents[torrent_id]
+
+ for label_id, options in self.labels.items():
+ if options['auto_add']:
+ if self._has_auto_match(torrent, options):
+ self.set_torrent(torrent_id, label_id)
+ return
+
+ def post_torrent_remove(self, torrent_id):
+ log.debug('post_torrent_remove')
+ if torrent_id in self.torrent_labels:
+ del self.torrent_labels[torrent_id]
+
+ # Utils #
+ def clean_config(self):
+ """remove invalid data from config-file"""
+ for torrent_id, label_id in list(self.torrent_labels.items()):
+ if (label_id not in self.labels) or (torrent_id not in self.torrents):
+ log.debug('label: rm %s:%s', torrent_id, label_id)
+ del self.torrent_labels[torrent_id]
+
+ def clean_initial_config(self):
+ """
+ *add any new keys in OPTIONS_DEFAULTS
+ *set all None values to default <-fix development config
+ """
+ log.debug(list(self.labels))
+ for key in self.labels:
+ options = dict(OPTIONS_DEFAULTS)
+ options.update(self.labels[key])
+ self.labels[key] = options
+
+ for label, options in self.labels.items():
+ for key, value in options.items():
+ if value is None:
+ self.labels[label][key] = OPTIONS_DEFAULTS[key]
+
+ def save_config(self):
+ self.clean_config()
+ self.config.save()
+
+ @export
+ def get_labels(self):
+ return sorted(self.labels)
+
+ # Labels:
+ @export
+ def add(self, label_id):
+ """add a label
+ see label_set_options for more options.
+ """
+ label_id = label_id.lower()
+ check_input(
+ RE_VALID.match(label_id), _('Invalid label, valid characters:[a-z0-9_-]')
+ )
+ check_input(label_id, _('Empty Label'))
+ check_input(not (label_id in self.labels), _('Label already exists'))
+
+ self.labels[label_id] = dict(OPTIONS_DEFAULTS)
+ self.config.save()
+
+ @export
+ def remove(self, label_id):
+ """remove a label"""
+ check_input(label_id in self.labels, _('Unknown Label'))
+ del self.labels[label_id]
+ self.clean_config()
+ self.config.save()
+
+ def _set_torrent_options(self, torrent_id, label_id):
+ options = self.labels[label_id]
+ torrent = self.torrents[torrent_id]
+
+ if not options['move_completed_path']:
+ options['move_completed_path'] = '' # no None.
+
+ if options['apply_max']:
+ torrent.set_max_download_speed(options['max_download_speed'])
+ torrent.set_max_upload_speed(options['max_upload_speed'])
+ torrent.set_max_connections(options['max_connections'])
+ torrent.set_max_upload_slots(options['max_upload_slots'])
+ torrent.set_prioritize_first_last_pieces(options['prioritize_first_last'])
+
+ if options['apply_queue']:
+ torrent.set_auto_managed(options['is_auto_managed'])
+ torrent.set_stop_at_ratio(options['stop_at_ratio'])
+ torrent.set_stop_ratio(options['stop_ratio'])
+ torrent.set_remove_at_ratio(options['remove_at_ratio'])
+
+ if options['apply_move_completed']:
+ torrent.set_options(
+ {
+ 'move_completed': options['move_completed'],
+ 'move_completed_path': options['move_completed_path'],
+ }
+ )
+
+ def _unset_torrent_options(self, torrent_id, label_id):
+ options = self.labels[label_id]
+ torrent = self.torrents[torrent_id]
+
+ if options['apply_max']:
+ torrent.set_max_download_speed(
+ self.core_cfg.config['max_download_speed_per_torrent']
+ )
+ torrent.set_max_upload_speed(
+ self.core_cfg.config['max_upload_speed_per_torrent']
+ )
+ torrent.set_max_connections(
+ self.core_cfg.config['max_connections_per_torrent']
+ )
+ torrent.set_max_upload_slots(
+ self.core_cfg.config['max_upload_slots_per_torrent']
+ )
+ torrent.set_prioritize_first_last_pieces(
+ self.core_cfg.config['prioritize_first_last_pieces']
+ )
+
+ if options['apply_queue']:
+ torrent.set_auto_managed(self.core_cfg.config['auto_managed'])
+ torrent.set_stop_at_ratio(self.core_cfg.config['stop_seed_at_ratio'])
+ torrent.set_stop_ratio(self.core_cfg.config['stop_seed_ratio'])
+ torrent.set_remove_at_ratio(self.core_cfg.config['remove_seed_at_ratio'])
+
+ if options['apply_move_completed']:
+ torrent.set_options(
+ {
+ 'move_completed': self.core_cfg.config['move_completed'],
+ 'move_completed_path': self.core_cfg.config['move_completed_path'],
+ }
+ )
+
+ def _has_auto_match(self, torrent, label_options):
+ """match for auto_add fields"""
+ for tracker_match in label_options['auto_add_trackers']:
+ for tracker in torrent.trackers:
+ if tracker_match in tracker['url']:
+ return True
+ return False
+
+ @export
+ def set_options(self, label_id, options_dict):
+ """update the label options
+
+ options_dict :
+ {"max_download_speed":float(),
+ "max_upload_speed":float(),
+ "max_connections":int(),
+ "max_upload_slots":int(),
+ #"prioritize_first_last":bool(),
+ "apply_max":bool(),
+ "move_completed_to":string() or None
+ }
+ """
+ check_input(label_id in self.labels, _('Unknown Label'))
+ for key in options_dict:
+ if key not in OPTIONS_DEFAULTS:
+ raise Exception('label: Invalid options_dict key:%s' % key)
+
+ self.labels[label_id].update(options_dict)
+
+ # apply
+ for torrent_id, label in self.torrent_labels.items():
+ if label_id == label and torrent_id in self.torrents:
+ self._set_torrent_options(torrent_id, label_id)
+
+ # auto add
+ options = self.labels[label_id]
+ if options['auto_add']:
+ for torrent_id, torrent in self.torrents.items():
+ if self._has_auto_match(torrent, options):
+ self.set_torrent(torrent_id, label_id)
+
+ self.config.save()
+
+ @export
+ def get_options(self, label_id):
+ """returns the label options"""
+ return self.labels[label_id]
+
+ @export
+ def set_torrent(self, torrent_id, label_id):
+ """
+ assign a label to a torrent
+ removes a label if the label_id parameter is empty.
+ """
+ if label_id == NO_LABEL:
+ label_id = None
+
+ check_input((not label_id) or (label_id in self.labels), _('Unknown Label'))
+ check_input(torrent_id in self.torrents, _('Unknown Torrent'))
+
+ if torrent_id in self.torrent_labels:
+ self._unset_torrent_options(torrent_id, self.torrent_labels[torrent_id])
+ del self.torrent_labels[torrent_id]
+ self.clean_config()
+ if label_id:
+ self.torrent_labels[torrent_id] = label_id
+ self._set_torrent_options(torrent_id, label_id)
+
+ self.config.save()
+
+ @export
+ def get_config(self):
+ """see : label_set_config"""
+ return {
+ key: self.config[key] for key in CORE_OPTIONS if key in self.config.config
+ }
+
+ @export
+ def set_config(self, options):
+ """global_options:"""
+ if options:
+ for key, value in options.items:
+ if key in CORE_OPTIONS:
+ self.config[key] = value
+
+ self.config.save()
+
+ def _status_get_label(self, torrent_id):
+ return self.torrent_labels.get(torrent_id) or ''
diff --git a/deluge/plugins/Label/deluge_label/data/label.js b/deluge/plugins/Label/deluge_label/data/label.js
new file mode 100644
index 0000000..cb0c1fe
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/data/label.js
@@ -0,0 +1,642 @@
+/**
+ * label.js
+ *
+ * Copyright (C) Damien Churchill 2010 <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.
+ *
+ */
+
+Ext.ns('Deluge.ux.preferences');
+
+/**
+ * @class Deluge.ux.preferences.LabelPage
+ * @extends Ext.Panel
+ */
+Deluge.ux.preferences.LabelPage = Ext.extend(Ext.Panel, {
+ title: _('Label'),
+ layout: 'fit',
+ border: false,
+
+ initComponent: function() {
+ Deluge.ux.preferences.LabelPage.superclass.initComponent.call(this);
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Label Preferences'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'panel',
+ });
+ fieldset.add({
+ border: false,
+ bodyCfg: {
+ html: _(
+ '<p>The Label plugin is enabled.</p><br>' +
+ '<p>To add, remove or edit labels right-click on the Label filter ' +
+ 'entry in the sidebar.</p><br>' +
+ '<p>To apply a label right-click on torrent(s).<p>'
+ ),
+ },
+ });
+ },
+});
+
+Ext.ns('Deluge.ux');
+
+/**
+ * @class Deluge.ux.AddLabelWindow
+ * @extends Ext.Window
+ */
+Deluge.ux.AddLabelWindow = Ext.extend(Ext.Window, {
+ title: _('Add Label'),
+ width: 300,
+ height: 100,
+
+ initComponent: function() {
+ Deluge.ux.AddLabelWindow.superclass.initComponent.call(this);
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Ok'), this.onOkClick, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ height: 35,
+ baseCls: 'x-plain',
+ bodyStyle: 'padding:5px 5px 0',
+ defaultType: 'textfield',
+ labelWidth: 50,
+ items: [
+ {
+ fieldLabel: _('Name'),
+ name: 'name',
+ allowBlank: false,
+ width: 220,
+ listeners: {
+ specialkey: {
+ fn: function(field, e) {
+ if (e.getKey() == 13) this.onOkClick();
+ },
+ scope: this,
+ },
+ },
+ },
+ ],
+ });
+ },
+
+ onCancelClick: function() {
+ this.hide();
+ },
+
+ onOkClick: function() {
+ var label = this.form.getForm().getValues().name;
+ deluge.client.label.add(label, {
+ success: function() {
+ deluge.ui.update();
+ this.fireEvent('labeladded', label);
+ },
+ scope: this,
+ });
+ this.hide();
+ },
+
+ onHide: function(comp) {
+ Deluge.ux.AddLabelWindow.superclass.onHide.call(this, comp);
+ this.form.getForm().reset();
+ },
+
+ onShow: function(comp) {
+ Deluge.ux.AddLabelWindow.superclass.onShow.call(this, comp);
+ this.form
+ .getForm()
+ .findField('name')
+ .focus(false, 150);
+ },
+});
+
+/**
+ * @class Deluge.ux.LabelOptionsWindow
+ * @extends Ext.Window
+ */
+Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, {
+ title: _('Label Options'),
+ width: 325,
+ height: 240,
+
+ initComponent: function() {
+ Deluge.ux.LabelOptionsWindow.superclass.initComponent.call(this);
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Ok'), this.onOkClick, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ });
+
+ this.tabs = this.form.add({
+ xtype: 'tabpanel',
+ height: 175,
+ border: false,
+ items: [
+ {
+ title: _('Maximum'),
+ items: [
+ {
+ border: false,
+ items: [
+ {
+ xtype: 'fieldset',
+ border: false,
+ labelWidth: 1,
+ style:
+ 'margin-bottom: 0px; padding-bottom: 0px;',
+ items: [
+ {
+ xtype: 'checkbox',
+ name: 'apply_max',
+ fieldLabel: '',
+ boxLabel: _(
+ 'Apply per torrent max settings:'
+ ),
+ listeners: {
+ check: this.onFieldChecked,
+ },
+ },
+ ],
+ },
+ {
+ xtype: 'fieldset',
+ border: false,
+ defaultType: 'spinnerfield',
+ style: 'margin-top: 0px; padding-top: 0px;',
+ items: [
+ {
+ fieldLabel: _('Download Speed'),
+ name: 'max_download_speed',
+ width: 80,
+ disabled: true,
+ value: -1,
+ minValue: -1,
+ },
+ {
+ fieldLabel: _('Upload Speed'),
+ name: 'max_upload_speed',
+ width: 80,
+ disabled: true,
+ value: -1,
+ minValue: -1,
+ },
+ {
+ fieldLabel: _('Upload Slots'),
+ name: 'max_upload_slots',
+ width: 80,
+ disabled: true,
+ value: -1,
+ minValue: -1,
+ },
+ {
+ fieldLabel: _('Connections'),
+ name: 'max_connections',
+ width: 80,
+ disabled: true,
+ value: -1,
+ minValue: -1,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: _('Queue'),
+ items: [
+ {
+ border: false,
+ items: [
+ {
+ xtype: 'fieldset',
+ border: false,
+ labelWidth: 1,
+ style:
+ 'margin-bottom: 0px; padding-bottom: 0px;',
+ items: [
+ {
+ xtype: 'checkbox',
+ name: 'apply_queue',
+ fieldLabel: '',
+ boxLabel: _(
+ 'Apply queue settings:'
+ ),
+ listeners: {
+ check: this.onFieldChecked,
+ },
+ },
+ ],
+ },
+ {
+ xtype: 'fieldset',
+ border: false,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ style: 'margin-top: 0px; padding-top: 0px;',
+ defaults: {
+ style: 'margin-left: 20px',
+ },
+ items: [
+ {
+ boxLabel: _('Auto Managed'),
+ name: 'is_auto_managed',
+ disabled: true,
+ },
+ {
+ boxLabel: _('Stop seed at ratio:'),
+ name: 'stop_at_ratio',
+ disabled: true,
+ },
+ {
+ xtype: 'spinnerfield',
+ name: 'stop_ratio',
+ width: 60,
+ decimalPrecision: 2,
+ incrementValue: 0.1,
+ style:
+ 'position: relative; left: 100px',
+ disabled: true,
+ },
+ {
+ boxLabel: _('Remove at ratio'),
+ name: 'remove_at_ratio',
+ disabled: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: _('Folders'),
+ items: [
+ {
+ border: false,
+ items: [
+ {
+ xtype: 'fieldset',
+ border: false,
+ labelWidth: 1,
+ style:
+ 'margin-bottom: 0px; padding-bottom: 0px;',
+ items: [
+ {
+ xtype: 'checkbox',
+ name: 'apply_move_completed',
+ fieldLabel: '',
+ boxLabel: _(
+ 'Apply folder settings:'
+ ),
+ listeners: {
+ check: this.onFieldChecked,
+ },
+ },
+ ],
+ },
+ {
+ xtype: 'fieldset',
+ border: false,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ labelWidth: 1,
+ style: 'margin-top: 0px; padding-top: 0px;',
+ defaults: {
+ style: 'margin-left: 20px',
+ },
+ items: [
+ {
+ boxLabel: _('Move completed to:'),
+ name: 'move_completed',
+ disabled: true,
+ },
+ {
+ xtype: 'textfield',
+ name: 'move_completed_path',
+ width: 250,
+ disabled: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ title: _('Trackers'),
+ items: [
+ {
+ border: false,
+ items: [
+ {
+ xtype: 'fieldset',
+ border: false,
+ labelWidth: 1,
+ style:
+ 'margin-bottom: 0px; padding-bottom: 0px;',
+ items: [
+ {
+ xtype: 'checkbox',
+ name: 'auto_add',
+ fieldLabel: '',
+ boxLabel: _(
+ 'Automatically apply label:'
+ ),
+ listeners: {
+ check: this.onFieldChecked,
+ },
+ },
+ ],
+ },
+ {
+ xtype: 'fieldset',
+ border: false,
+ labelWidth: 1,
+ style: 'margin-top: 0px; padding-top: 0px;',
+ defaults: {
+ style: 'margin-left: 20px',
+ },
+ defaultType: 'textarea',
+ items: [
+ {
+ boxLabel: _('Move completed to:'),
+ name: 'auto_add_trackers',
+ width: 250,
+ height: 100,
+ disabled: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+ },
+
+ getLabelOptions: function() {
+ deluge.client.label.get_options(this.label, {
+ success: this.gotOptions,
+ scope: this,
+ });
+ },
+
+ gotOptions: function(options) {
+ this.form.getForm().setValues(options);
+ },
+
+ show: function(label) {
+ Deluge.ux.LabelOptionsWindow.superclass.show.call(this);
+ this.label = label;
+ this.setTitle(_('Label Options') + ': ' + this.label);
+ this.tabs.setActiveTab(0);
+ this.getLabelOptions();
+ },
+
+ onCancelClick: function() {
+ this.hide();
+ },
+
+ onOkClick: function() {
+ var values = this.form.getForm().getFieldValues();
+ if (values['auto_add_trackers']) {
+ values['auto_add_trackers'] = values['auto_add_trackers'].split(
+ '\n'
+ );
+ }
+ deluge.client.label.set_options(this.label, values);
+ this.hide();
+ },
+
+ onFieldChecked: function(field, checked) {
+ var fs = field.ownerCt.nextSibling();
+ fs.items.each(function(field) {
+ field.setDisabled(!checked);
+ });
+ },
+});
+
+Ext.ns('Deluge.plugins');
+
+/**
+ * @class Deluge.plugins.LabelPlugin
+ * @extends Deluge.Plugin
+ */
+Deluge.plugins.LabelPlugin = Ext.extend(Deluge.Plugin, {
+ name: 'Label',
+
+ createMenu: function() {
+ this.labelMenu = new Ext.menu.Menu({
+ items: [
+ {
+ text: _('Add Label'),
+ iconCls: 'icon-add',
+ handler: this.onLabelAddClick,
+ scope: this,
+ },
+ {
+ text: _('Remove Label'),
+ disabled: true,
+ iconCls: 'icon-remove',
+ handler: this.onLabelRemoveClick,
+ scope: this,
+ },
+ {
+ text: _('Label Options'),
+ disabled: true,
+ handler: this.onLabelOptionsClick,
+ scope: this,
+ },
+ ],
+ });
+ },
+
+ setFilter: function(filter) {
+ filter.show_zero = true;
+
+ filter.list.on('contextmenu', this.onLabelContextMenu, this);
+ filter.header.on('contextmenu', this.onLabelHeaderContextMenu, this);
+ this.filter = filter;
+ },
+
+ updateTorrentMenu: function(states) {
+ this.torrentMenu.removeAll(true);
+ this.torrentMenu.addMenuItem({
+ text: _('No Label'),
+ label: '',
+ handler: this.onTorrentMenuClick,
+ scope: this,
+ });
+ for (var state in states) {
+ if (!state || state == 'All') continue;
+ this.torrentMenu.addMenuItem({
+ text: state,
+ label: state,
+ handler: this.onTorrentMenuClick,
+ scope: this,
+ });
+ }
+ },
+
+ onDisable: function() {
+ deluge.sidebar.un('filtercreate', this.onFilterCreate);
+ deluge.sidebar.un('afterfiltercreate', this.onAfterFilterCreate);
+ delete Deluge.FilterPanel.templates.label;
+ this.deregisterTorrentStatus('label');
+ deluge.menus.torrent.remove(this.tmSep);
+ deluge.menus.torrent.remove(this.tm);
+ deluge.preferences.removePage(this.prefsPage);
+ },
+
+ onEnable: function() {
+ this.prefsPage = deluge.preferences.addPage(
+ new Deluge.ux.preferences.LabelPage()
+ );
+ this.torrentMenu = new Ext.menu.Menu();
+
+ this.tmSep = deluge.menus.torrent.add({
+ xtype: 'menuseparator',
+ });
+
+ this.tm = deluge.menus.torrent.add({
+ text: _('Label'),
+ menu: this.torrentMenu,
+ });
+
+ var lbltpl =
+ '<div class="x-deluge-filter">' +
+ '<tpl if="filter">{filter}</tpl>' +
+ '<tpl if="!filter">No Label</tpl>' +
+ ' ({count})' +
+ '</div>';
+
+ if (deluge.sidebar.hasFilter('label')) {
+ var filter = deluge.sidebar.getFilter('label');
+ filter.list.columns[0].tpl = new Ext.XTemplate(lbltpl);
+ this.setFilter(filter);
+ this.updateTorrentMenu(filter.getStates());
+ filter.list.refresh();
+ } else {
+ deluge.sidebar.on('filtercreate', this.onFilterCreate, this);
+ deluge.sidebar.on(
+ 'afterfiltercreate',
+ this.onAfterFilterCreate,
+ this
+ );
+ Deluge.FilterPanel.templates.label = lbltpl;
+ }
+ this.registerTorrentStatus('label', _('Label'));
+ },
+
+ onAfterFilterCreate: function(sidebar, filter) {
+ if (filter.filter != 'label') return;
+ this.updateTorrentMenu(filter.getStates());
+ },
+
+ onFilterCreate: function(sidebar, filter) {
+ if (filter.filter != 'label') return;
+ this.setFilter(filter);
+ },
+
+ onLabelAddClick: function() {
+ if (!this.addWindow) {
+ this.addWindow = new Deluge.ux.AddLabelWindow();
+ this.addWindow.on('labeladded', this.onLabelAdded, this);
+ }
+ this.addWindow.show();
+ },
+
+ onLabelAdded: function(label) {
+ var filter = deluge.sidebar.getFilter('label');
+ var states = filter.getStates();
+ var statesArray = [];
+
+ for (state in states) {
+ if (!state || state == 'All') continue;
+ statesArray.push(state);
+ }
+
+ statesArray.push(label.toLowerCase());
+ statesArray.sort();
+
+ //console.log(states);
+ //console.log(statesArray);
+
+ states = {};
+
+ for (i = 0; i < statesArray.length; ++i) {
+ states[statesArray[i]] = 0;
+ }
+
+ this.updateTorrentMenu(states);
+ },
+
+ onLabelContextMenu: function(dv, i, node, e) {
+ e.preventDefault();
+ if (!this.labelMenu) this.createMenu();
+ var r = dv.getRecord(node).get('filter');
+ if (!r || r == 'All') {
+ this.labelMenu.items.get(1).setDisabled(true);
+ this.labelMenu.items.get(2).setDisabled(true);
+ } else {
+ this.labelMenu.items.get(1).setDisabled(false);
+ this.labelMenu.items.get(2).setDisabled(false);
+ }
+ dv.select(i);
+ this.labelMenu.showAt(e.getXY());
+ },
+
+ onLabelHeaderContextMenu: function(e, t) {
+ e.preventDefault();
+ if (!this.labelMenu) this.createMenu();
+ this.labelMenu.items.get(1).setDisabled(true);
+ this.labelMenu.items.get(2).setDisabled(true);
+ this.labelMenu.showAt(e.getXY());
+ },
+
+ onLabelOptionsClick: function() {
+ if (!this.labelOpts)
+ this.labelOpts = new Deluge.ux.LabelOptionsWindow();
+ this.labelOpts.show(this.filter.getState());
+ },
+
+ onLabelRemoveClick: function() {
+ var state = this.filter.getState();
+ deluge.client.label.remove(state, {
+ success: function() {
+ deluge.ui.update();
+ this.torrentMenu.items.each(function(item) {
+ if (item.text != state) return;
+ this.torrentMenu.remove(item);
+ var i = item;
+ }, this);
+ },
+ scope: this,
+ });
+ },
+
+ onTorrentMenuClick: function(item, e) {
+ var ids = deluge.torrents.getSelectedIds();
+ Ext.each(ids, function(id, i) {
+ if (ids.length == i + 1) {
+ deluge.client.label.set_torrent(id, item.label, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ } else {
+ deluge.client.label.set_torrent(id, item.label);
+ }
+ });
+ },
+});
+Deluge.registerPlugin('Label', Deluge.plugins.LabelPlugin);
diff --git a/deluge/plugins/Label/deluge_label/data/label_add.ui b/deluge/plugins/Label/deluge_label/data/label_add.ui
new file mode 100644
index 0000000..68f8a72
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/data/label_add.ui
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="dlg_label_add">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Add Label</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">mouse</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_add_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="button1">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_add_ok" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</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="spacing">5</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-add</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Add Label&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Name:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="txt_add">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="activates_default">True</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">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button2</action-widget>
+ <action-widget response="0">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/plugins/Label/deluge_label/data/label_options.ui b/deluge/plugins/Label/deluge_label/data/label_options.ui
new file mode 100644
index 0000000..c0fca4f
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/data/label_options.ui
@@ -0,0 +1,735 @@
+<?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">9999</property>
+ <property name="value">-1</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">9999</property>
+ <property name="value">-1</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">9999</property>
+ <property name="value">-1</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">9999</property>
+ <property name="value">-1</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment5">
+ <property name="lower">0.10000000000000001</property>
+ <property name="upper">100</property>
+ <property name="value">2</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkTextBuffer" id="textbuffer1">
+ <property name="text" translatable="yes">tracker1.org</property>
+ </object>
+ <object class="GtkDialog" id="dlg_label_options">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Label Options</property>
+ <property name="modal">True</property>
+ <property name="window_position">mouse</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <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="spacing">5</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="button4">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_options_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="button3">
+ <property name="label">gtk-ok</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_options_ok" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</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="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-preferences</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_header">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Label Options&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">2</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="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">4</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">5</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="right_attach">4</property>
+ <property name="y_options">GTK_FILL</property>
+ </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">adjustment1</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="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label24">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">KiB/s</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">GTK_FILL</property>
+ </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">adjustment2</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Upload Slots:</property>
+ <property name="xalign">0</property>
+ </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">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Upload Speed:</property>
+ <property name="xalign">0</property>
+ </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">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Download Speed:</property>
+ <property name="use_markup">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">KiB/s</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">GTK_FILL</property>
+ </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">adjustment3</property>
+ <property name="numeric">True</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="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Connections:</property>
+ <property name="xalign">0</property>
+ </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">GTK_FILL</property>
+ </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">adjustment4</property>
+ <property name="numeric">True</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="x_options"/>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <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>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkCheckButton" id="apply_max">
+ <property name="label" translatable="yes">Apply per torrent max settings:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Maximum</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">2</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <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">4</property>
+ <property name="n_columns">3</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</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="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="is_auto_managed">
+ <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="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="stop_at_ratio">
+ <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="draw_indicator">True</property>
+ </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">GTK_FILL</property>
+ </packing>
+ </child>
+ <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="draw_indicator">True</property>
+ </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">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="stop_ratio">
+ <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">adjustment5</property>
+ <property name="digits">2</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="x_options"/>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkCheckButton" id="apply_queue">
+ <property name="label" translatable="yes">Apply Queue settings:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Queue</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">2</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="GtkTable" id="table3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkCheckButton" id="move_completed">
+ <property name="label" translatable="yes">Move completed 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>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="move_completed_path">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action">select-folder</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="move_completed_path_entry">
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</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">GTK_FILL</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkCheckButton" id="apply_move_completed">
+ <property name="label" translatable="yes">Apply folder settings:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Folders</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">2</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="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="auto_add_trackers">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="buffer">textbuffer1</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="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;i&gt;(1 line per tracker)&lt;/i&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkCheckButton" id="auto_add">
+ <property name="label" translatable="yes">Automatically apply label:</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>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label99">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Trackers</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button4</action-widget>
+ <action-widget response="0">button3</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/plugins/Label/deluge_label/data/label_pref.ui b/deluge/plugins/Label/deluge_label/data/label_pref.ui
new file mode 100644
index 0000000..81edc37
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/data/label_pref.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkWindow" id="window1">
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="label_prefs_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <property name="orientation">vertical</property>
+ <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="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;i&gt;Use the sidebar to add,edit and remove labels. &lt;/i&gt;
+</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Labels&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>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/plugins/Label/deluge_label/gtkui/__init__.py b/deluge/plugins/Label/deluge_label/gtkui/__init__.py
new file mode 100644
index 0000000..eeaeadc
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/gtkui/__init__.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@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 import component # for systray
+from deluge.plugins.pluginbase import Gtk3PluginBase
+
+from . import label_config, sidebar_menu, submenu
+
+log = logging.getLogger(__name__)
+
+NO_LABEL = 'No Label'
+
+
+def cell_data_label(column, cell, model, row, data):
+ cell.set_property('text', str(model.get_value(row, data)))
+
+
+class GtkUI(Gtk3PluginBase):
+ def start(self):
+ if self.label_menu:
+ self.label_menu.on_show()
+
+ def enable(self):
+ self.plugin = component.get('PluginManager')
+ self.torrentmenu = component.get('MenuBar').torrentmenu
+ self.label_menu = None
+ self.labelcfg = None
+ self.sidebar_menu = None
+ self.load_interface()
+
+ def disable(self):
+ if self.label_menu in self.torrentmenu.get_children():
+ self.torrentmenu.remove(self.label_menu)
+
+ self.labelcfg.unload()
+ self.sidebar_menu.unload()
+ del self.sidebar_menu
+
+ component.get('TorrentView').remove_column(_('Label'))
+
+ def load_interface(self):
+ # sidebar
+ # disabled
+ if not self.sidebar_menu:
+ self.sidebar_menu = sidebar_menu.LabelSidebarMenu()
+ # self.sidebar.load()
+
+ # menu:
+ log.debug('add items to torrentview-popup menu.')
+ self.label_menu = submenu.LabelMenu()
+ self.torrentmenu.append(self.label_menu)
+ self.label_menu.show_all()
+
+ # columns:
+ self.load_columns()
+
+ # config:
+ if not self.labelcfg:
+ self.labelcfg = label_config.LabelConfig(self.plugin)
+ self.labelcfg.load()
+
+ log.debug('Finished loading Label plugin')
+
+ def load_columns(self):
+ log.debug('add columns')
+
+ component.get('TorrentView').add_text_column(_('Label'), status_field=['label'])
diff --git a/deluge/plugins/Label/deluge_label/gtkui/label_config.py b/deluge/plugins/Label/deluge_label/gtkui/label_config.py
new file mode 100644
index 0000000..b1bf56d
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/gtkui/label_config.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@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 gi.repository.Gtk import Builder
+
+from deluge.ui.client import client
+
+from ..common import get_resource
+
+log = logging.getLogger(__name__)
+
+
+class LabelConfig(object):
+ """
+ there used to be some options here...
+ """
+
+ def __init__(self, plugin):
+ self.plugin = plugin
+
+ def load(self):
+ log.debug('Adding Label Preferences page')
+ builder = Builder()
+ builder.add_from_file(get_resource('label_pref.ui'))
+
+ self.plugin.add_preferences_page(
+ _('Label'), builder.get_object('label_prefs_box')
+ )
+ self.plugin.register_hook('on_show_prefs', self.load_settings)
+ self.plugin.register_hook('on_apply_prefs', self.on_apply_prefs)
+
+ self.load_settings()
+
+ def unload(self):
+ self.plugin.remove_preferences_page(_('Label'))
+ self.plugin.deregister_hook('on_apply_prefs', self.on_apply_prefs)
+ self.plugin.deregister_hook('on_show_prefs', self.load_settings)
+
+ def load_settings(self, widget=None, data=None):
+ client.label.get_config().addCallback(self.cb_global_options)
+
+ def cb_global_options(self, options):
+ log.debug('options=%s', options)
+
+ # for id in self.chk_ids:
+ # self.glade.get_widget(id).set_active(bool(options[id]))
+
+ def on_apply_prefs(self):
+ options = {}
+ # update options dict here.
+ client.label.set_config(options)
diff --git a/deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py b/deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py
new file mode 100644
index 0000000..34358db
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import gi # isort:skip (Required before Gtk import).
+
+gi.require_version('Gtk', '3.0') # NOQA: E402
+
+# isort:imports-thirdparty
+from gi.repository import Gtk
+
+# isort:imports-firstparty
+import deluge.component as component
+from deluge.ui.client import client
+
+# isort:imports-localfolder
+from ..common import get_resource
+
+log = logging.getLogger(__name__)
+
+NO_LABEL = 'No Label'
+
+
+# menu
+class LabelSidebarMenu(object):
+ def __init__(self):
+
+ self.treeview = component.get('FilterTreeView')
+ self.menu = self.treeview.menu
+ self.items = []
+
+ # add items, in reverse order, because they are prepended.
+ sep = Gtk.SeparatorMenuItem()
+ self.items.append(sep)
+ self.menu.prepend(sep)
+ self._add_item('options', _('Label _Options'))
+ self._add_item('remove', _('_Remove Label'))
+ self._add_item('add', _('_Add Label'))
+
+ self.menu.show_all()
+ # dialogs:
+ self.add_dialog = AddDialog()
+ self.options_dialog = OptionsDialog()
+ # hooks:
+ self.menu.connect('show', self.on_show, None)
+
+ def _add_item(self, item_id, label):
+ """
+ id is automatically-added as self.item_<id>
+ """
+ item = Gtk.MenuItem.new_with_mnemonic(label)
+ func = getattr(self, 'on_%s' % item_id)
+ item.connect('activate', func)
+ self.menu.prepend(item)
+ setattr(self, 'item_%s' % item_id, item)
+ self.items.append(item)
+ return item
+
+ def on_add(self, event=None):
+ self.add_dialog.show()
+
+ def on_remove(self, event=None):
+ client.label.remove(self.treeview.value)
+
+ def on_options(self, event=None):
+ self.options_dialog.show(self.treeview.value)
+
+ def on_show(self, widget=None, data=None):
+ """No Label:disable options/del."""
+ log.debug('label-sidebar-popup:on-show')
+
+ cat = self.treeview.cat
+ label = self.treeview.value
+ if cat == 'label' or (cat == 'cat' and label == 'label'):
+ # is a label : show menu-items
+ for item in self.items:
+ item.show()
+ # default items
+ sensitive = (label not in (NO_LABEL, None, '', 'All')) and (cat != 'cat')
+ for item in self.items:
+ item.set_sensitive(sensitive)
+
+ # add is allways enabled.
+ self.item_add.set_sensitive(True)
+ else:
+ # not a label -->hide everything.
+ for item in self.items:
+ item.hide()
+
+ def unload(self):
+ log.debug('disable01')
+ for item in list(self.items):
+ item.hide()
+ item.destroy()
+ log.debug('disable02')
+ self.items = []
+
+
+# dialogs:
+class AddDialog(object):
+ def __init__(self):
+ pass
+
+ def show(self):
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(get_resource('label_add.ui'))
+ self.dialog = self.builder.get_object('dlg_label_add')
+ self.dialog.set_transient_for(component.get('MainWindow').window)
+
+ self.builder.connect_signals(self)
+ self.dialog.run()
+
+ def on_add_ok(self, event=None):
+ value = self.builder.get_object('txt_add').get_text()
+ client.label.add(value)
+ self.dialog.destroy()
+
+ def on_add_cancel(self, event=None):
+ self.dialog.destroy()
+
+
+class OptionsDialog(object):
+ spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
+ spin_int_ids = ['max_upload_slots', 'max_connections']
+ chk_ids = [
+ 'apply_max',
+ 'apply_queue',
+ 'stop_at_ratio',
+ 'apply_queue',
+ 'remove_at_ratio',
+ 'apply_move_completed',
+ 'move_completed',
+ 'is_auto_managed',
+ 'auto_add',
+ ]
+
+ # list of tuples, because order matters when nesting.
+ sensitive_groups = [
+ (
+ 'apply_max',
+ [
+ 'max_download_speed',
+ 'max_upload_speed',
+ 'max_upload_slots',
+ 'max_connections',
+ ],
+ ),
+ ('apply_queue', ['is_auto_managed', 'stop_at_ratio']),
+ ('stop_at_ratio', ['remove_at_ratio', 'stop_ratio']), # nested
+ ('apply_move_completed', ['move_completed']),
+ ('move_completed', ['move_completed_path']), # nested
+ ('auto_add', ['auto_add_trackers']),
+ ]
+
+ def __init__(self):
+ pass
+
+ def show(self, label):
+ self.label = label
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(get_resource('label_options.ui'))
+ self.dialog = self.builder.get_object('dlg_label_options')
+ self.dialog.set_transient_for(component.get('MainWindow').window)
+ self.builder.connect_signals(self)
+ # Show the label name in the header label
+ self.builder.get_object('label_header').set_markup(
+ '<b>%s:</b> %s' % (_('Label Options'), self.label)
+ )
+
+ for chk_id, group in self.sensitive_groups:
+ chk = self.builder.get_object(chk_id)
+ chk.connect('toggled', self.apply_sensitivity)
+
+ client.label.get_options(self.label).addCallback(self.load_options)
+
+ self.dialog.run()
+
+ def load_options(self, options):
+ log.debug(list(options))
+
+ for spin_id in self.spin_ids + self.spin_int_ids:
+ self.builder.get_object(spin_id).set_value(options[spin_id])
+ for chk_id in self.chk_ids:
+ self.builder.get_object(chk_id).set_active(bool(options[chk_id]))
+
+ if client.is_localhost():
+ self.builder.get_object('move_completed_path').set_filename(
+ options['move_completed_path']
+ )
+ self.builder.get_object('move_completed_path').show()
+ self.builder.get_object('move_completed_path_entry').hide()
+ else:
+ self.builder.get_object('move_completed_path_entry').set_text(
+ options['move_completed_path']
+ )
+ self.builder.get_object('move_completed_path_entry').show()
+ self.builder.get_object('move_completed_path').hide()
+
+ self.builder.get_object('auto_add_trackers').get_buffer().set_text(
+ '\n'.join(options['auto_add_trackers'])
+ )
+
+ self.apply_sensitivity()
+
+ def on_options_ok(self, event=None):
+ """Save options."""
+ options = {}
+
+ for spin_id in self.spin_ids:
+ options[spin_id] = self.builder.get_object(spin_id).get_value()
+ for spin_int_id in self.spin_int_ids:
+ options[spin_int_id] = self.builder.get_object(
+ spin_int_id
+ ).get_value_as_int()
+ for chk_id in self.chk_ids:
+ options[chk_id] = self.builder.get_object(chk_id).get_active()
+
+ if client.is_localhost():
+ options['move_completed_path'] = self.builder.get_object(
+ 'move_completed_path'
+ ).get_filename()
+ else:
+ options['move_completed_path'] = self.builder.get_object(
+ 'move_completed_path_entry'
+ ).get_text()
+
+ buff = self.builder.get_object(
+ 'auto_add_trackers'
+ ).get_buffer() # sometimes I hate gtk...
+ tracker_lst = (
+ buff.get_text(
+ buff.get_start_iter(), buff.get_end_iter(), include_hidden_chars=False
+ )
+ .strip()
+ .split('\n')
+ )
+ options['auto_add_trackers'] = [
+ x for x in tracker_lst if x
+ ] # filter out empty lines.
+
+ log.debug(options)
+ client.label.set_options(self.label, options)
+ self.dialog.destroy()
+
+ def apply_sensitivity(self, event=None):
+ for chk_id, sensitive_list in self.sensitive_groups:
+ chk = self.builder.get_object(chk_id)
+ sens = chk.get_active() and chk.get_property('sensitive')
+ for widget_id in sensitive_list:
+ self.builder.get_object(widget_id).set_sensitive(sens)
+
+ def on_options_cancel(self, event=None):
+ self.dialog.destroy()
diff --git a/deluge/plugins/Label/deluge_label/gtkui/submenu.py b/deluge/plugins/Label/deluge_label/gtkui/submenu.py
new file mode 100644
index 0000000..c5f80e7
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/gtkui/submenu.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@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 gi.repository.Gtk import Menu, MenuItem
+
+from deluge import component # for systray
+from deluge.ui.client import client
+
+log = logging.getLogger(__name__)
+
+
+# Deferred Translation
+def _(message):
+ return message
+
+
+NO_LABEL = _('No Label')
+del _
+
+
+class LabelMenu(MenuItem):
+ def __init__(self):
+ MenuItem.__init__(self, _('Label')) # noqa: F821
+
+ self.sub_menu = Menu()
+ self.set_submenu(self.sub_menu)
+ self.items = []
+
+ # attach..
+ self.sub_menu.connect('show', self.on_show, None)
+
+ def get_torrent_ids(self):
+ return component.get('TorrentView').get_selected_torrents()
+
+ def on_show(self, widget=None, data=None):
+ log.debug('label-on-show')
+ client.label.get_labels().addCallback(self.cb_labels)
+
+ def cb_labels(self, labels):
+ for child in self.sub_menu.get_children():
+ self.sub_menu.remove(child)
+ for label in [NO_LABEL] + list(labels):
+ if label == NO_LABEL:
+ item = MenuItem(_(NO_LABEL)) # noqa: F821
+ else:
+ item = MenuItem(label.replace('_', '__'))
+ item.connect('activate', self.on_select_label, label)
+ self.sub_menu.append(item)
+ self.show_all()
+
+ def on_select_label(self, widget=None, label_id=None):
+ log.debug('select label:%s,%s', label_id, self.get_torrent_ids())
+ for torrent_id in self.get_torrent_ids():
+ client.label.set_torrent(torrent_id, label_id)
diff --git a/deluge/plugins/Label/deluge_label/test.py b/deluge/plugins/Label/deluge_label/test.py
new file mode 100644
index 0000000..5c9ffcd
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/test.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@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 print_function, unicode_literals
+
+from deluge.ui.client import sclient
+
+sclient.set_core_uri()
+
+print(sclient.get_enabled_plugins())
+
+# enable plugin.
+if 'label' not in sclient.get_enabled_plugins():
+ sclient.enable_plugin('label')
+
+
+# test labels.
+print('#init labels')
+try:
+ sclient.label_remove('test')
+except Exception:
+ pass
+sess_id = sclient.get_session_state()[0]
+
+print('#add')
+sclient.label_add('test')
+print('#set')
+sclient.label_set_torrent(id, 'test')
+
+print(sclient.get_torrents_status({'label': 'test'}, 'name'))
+
+
+print('#set options')
+sclient.label_set_options('test', {'max_download_speed': 999}, True)
+print(sclient.get_torrent_status(sess_id, ['max_download_speed']), '999')
+sclient.label_set_options('test', {'max_download_speed': 9}, True)
+print(sclient.get_torrent_status(sess_id, ['max_download_speed']), '9')
+sclient.label_set_options('test', {'max_download_speed': 888}, False)
+print(sclient.get_torrent_status(sess_id, ['max_download_speed']), '9 (888)')
+
+print(sclient.get_torrent_status(sess_id, ['name', 'tracker_host', 'label']))
diff --git a/deluge/plugins/Label/deluge_label/webui.py b/deluge/plugins/Label/deluge_label/webui.py
new file mode 100644
index 0000000..58c38e9
--- /dev/null
+++ b/deluge/plugins/Label/deluge_label/webui.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+#
+# Basic plugin template created by:
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from deluge.plugins.pluginbase import WebPluginBase
+
+from .common import get_resource
+
+log = logging.getLogger(__name__)
+
+
+class WebUI(WebPluginBase):
+ scripts = [get_resource('label.js')]
+ debug_scripts = scripts
diff --git a/deluge/plugins/Label/setup.py b/deluge/plugins/Label/setup.py
new file mode 100644
index 0000000..567335b
--- /dev/null
+++ b/deluge/plugins/Label/setup.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@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__ = 'Label'
+__author__ = 'Martijn Voncken'
+__author_email__ = 'mvoncken@gmail.com'
+__version__ = '0.3'
+__url__ = 'http://deluge-torrent.org'
+__license__ = 'GPLv3'
+__description__ = 'Allows labels to be assigned to torrents'
+__long_description__ = """
+Allows labels to be assigned to torrents
+
+Also offers filters on state, tracker and keywords
+"""
+__pkg_data__ = {'deluge_' + __plugin_name__.lower(): ['data/*']}
+
+setup(
+ name=__plugin_name__,
+ version=__version__,
+ description=__description__,
+ author=__author__,
+ author_email=__author_email__,
+ url=__url__,
+ license=__license__,
+ long_description=__long_description__,
+ packages=find_packages(),
+ package_data=__pkg_data__,
+ entry_points="""
+ [deluge.plugin.core]
+ %s = deluge_%s:CorePlugin
+ [deluge.plugin.gtk3ui]
+ %s = deluge_%s:GtkUIPlugin
+ [deluge.plugin.web]
+ %s = deluge_%s:WebUIPlugin
+ """
+ % ((__plugin_name__, __plugin_name__.lower()) * 3),
+)