From 2e2851dc13d73352530dd4495c7e05603b2e520d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 23:38:38 +0200 Subject: Adding upstream version 2.1.2~dev0+20240219. Signed-off-by: Daniel Baumann --- .../plugins/Scheduler/deluge_scheduler/__init__.py | 37 ++ .../plugins/Scheduler/deluge_scheduler/common.py | 20 + deluge/plugins/Scheduler/deluge_scheduler/core.py | 167 ++++++ .../Scheduler/deluge_scheduler/data/green.svg | 1 + .../Scheduler/deluge_scheduler/data/red.svg | 1 + .../Scheduler/deluge_scheduler/data/scheduler.js | 621 +++++++++++++++++++++ .../Scheduler/deluge_scheduler/data/yellow.svg | 1 + deluge/plugins/Scheduler/deluge_scheduler/gtkui.py | 356 ++++++++++++ deluge/plugins/Scheduler/deluge_scheduler/webui.py | 23 + deluge/plugins/Scheduler/setup.py | 45 ++ 10 files changed, 1272 insertions(+) create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/__init__.py create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/common.py create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/core.py create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/data/green.svg create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/data/red.svg create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/data/scheduler.js create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/data/yellow.svg create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/gtkui.py create mode 100644 deluge/plugins/Scheduler/deluge_scheduler/webui.py create mode 100644 deluge/plugins/Scheduler/setup.py (limited to 'deluge/plugins/Scheduler') diff --git a/deluge/plugins/Scheduler/deluge_scheduler/__init__.py b/deluge/plugins/Scheduler/deluge_scheduler/__init__.py new file mode 100644 index 0000000..87d1584 --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/__init__.py @@ -0,0 +1,37 @@ +# +# Copyright (C) 2009 Andrew Resch +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +from deluge.plugins.init import PluginInitBase + + +class CorePlugin(PluginInitBase): + def __init__(self, plugin_name): + from .core import Core as _pluginCls + + self._plugin_cls = _pluginCls + super().__init__(plugin_name) + + +class GtkUIPlugin(PluginInitBase): + def __init__(self, plugin_name): + from .gtkui import GtkUI as _pluginCls + + self._plugin_cls = _pluginCls + super().__init__(plugin_name) + + +class WebUIPlugin(PluginInitBase): + def __init__(self, plugin_name): + from .webui import WebUI as _pluginCls + + self._plugin_cls = _pluginCls + super().__init__(plugin_name) diff --git a/deluge/plugins/Scheduler/deluge_scheduler/common.py b/deluge/plugins/Scheduler/deluge_scheduler/common.py new file mode 100644 index 0000000..eb47f13 --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/common.py @@ -0,0 +1,20 @@ +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# 2007-2009 Andrew Resch +# 2009 Damien Churchill +# 2010 Pedro Algarvio +# 2017 Calum Lind +# +# 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. +# + +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/Scheduler/deluge_scheduler/core.py b/deluge/plugins/Scheduler/deluge_scheduler/core.py new file mode 100644 index 0000000..10798ba --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/core.py @@ -0,0 +1,167 @@ +# +# Copyright (C) 2009 Andrew Resch +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +import logging +import time + +from twisted.internet import reactor + +import deluge.component as component +import deluge.configmanager +from deluge.core.rpcserver import export +from deluge.event import DelugeEvent +from deluge.plugins.pluginbase import CorePluginBase + +log = logging.getLogger(__name__) + +DEFAULT_PREFS = { + 'low_down': -1.0, + 'low_up': -1.0, + 'low_active': -1, + 'low_active_down': -1, + 'low_active_up': -1, + 'button_state': [[0] * 7 for dummy in range(24)], +} + +STATES = {0: 'Green', 1: 'Yellow', 2: 'Red'} + +CONTROLLED_SETTINGS = [ + 'max_download_speed', + 'max_upload_speed', + 'max_active_limit', + 'max_active_downloading', + 'max_active_seeding', +] + + +class SchedulerEvent(DelugeEvent): + """ + Emitted when a schedule state changes. + """ + + def __init__(self, colour): + """ + :param colour: str, the current scheduler state + """ + self._args = [colour] + + +class Core(CorePluginBase): + def enable(self): + # Create the defaults with the core config + core_config = component.get('Core').config + DEFAULT_PREFS['low_down'] = core_config['max_download_speed'] + DEFAULT_PREFS['low_up'] = core_config['max_upload_speed'] + DEFAULT_PREFS['low_active'] = core_config['max_active_limit'] + DEFAULT_PREFS['low_active_down'] = core_config['max_active_downloading'] + DEFAULT_PREFS['low_active_up'] = core_config['max_active_seeding'] + + self.config = deluge.configmanager.ConfigManager( + 'scheduler.conf', DEFAULT_PREFS + ) + + self.state = self.get_state() + + # Apply the scheduling rules + self.do_schedule(False) + + # Schedule the next do_schedule() call for on the next hour + now = time.localtime(time.time()) + secs_to_next_hour = ((60 - now[4]) * 60) + (60 - now[5]) + self.timer = reactor.callLater(secs_to_next_hour, self.do_schedule) + + # Register for config changes so state isn't overridden + component.get('EventManager').register_event_handler( + 'ConfigValueChangedEvent', self.on_config_value_changed + ) + + def disable(self): + if self.timer.active(): + self.timer.cancel() + component.get('EventManager').deregister_event_handler( + 'ConfigValueChangedEvent', self.on_config_value_changed + ) + self.__apply_set_functions() + + def update(self): + pass + + def on_config_value_changed(self, key, value): + if key in CONTROLLED_SETTINGS: + self.do_schedule(False) + + def __apply_set_functions(self): + """ + Have the core apply it's bandwidth settings as specified in core.conf. + """ + core_config = deluge.configmanager.ConfigManager('core.conf') + for setting in CONTROLLED_SETTINGS: + component.get('PreferencesManager').do_config_set_func( + setting, core_config[setting] + ) + # Resume the session if necessary + component.get('Core').resume_session() + + def do_schedule(self, timer=True): + """ + This is where we apply schedule rules. + """ + + state = self.get_state() + + if state == 'Green': + # This is Green (Normal) so we just make sure we've applied the + # global defaults + self.__apply_set_functions() + elif state == 'Yellow': + # This is Yellow (Slow), so use the settings provided from the user + settings = { + 'active_limit': self.config['low_active'], + 'active_downloads': self.config['low_active_down'], + 'active_seeds': self.config['low_active_up'], + 'download_rate_limit': int(self.config['low_down'] * 1024), + 'upload_rate_limit': int(self.config['low_up'] * 1024), + } + component.get('Core').apply_session_settings(settings) + # Resume the session if necessary + component.get('Core').resume_session() + elif state == 'Red': + # This is Red (Stop), so pause the libtorrent session + component.get('Core').pause_session() + + if state != self.state: + # The state has changed since last update so we need to emit an event + self.state = state + component.get('EventManager').emit(SchedulerEvent(self.state)) + + if timer: + # Call this again in 1 hour + self.timer = reactor.callLater(3600, self.do_schedule) + + @export() + def set_config(self, config): + """Sets the config dictionary.""" + for key in config: + self.config[key] = config[key] + self.config.save() + self.do_schedule(False) + + @export() + def get_config(self): + """Returns the config dictionary.""" + return self.config.config + + @export() + def get_state(self): + now = time.localtime(time.time()) + level = self.config['button_state'][now[3]][now[6]] + return STATES[level] diff --git a/deluge/plugins/Scheduler/deluge_scheduler/data/green.svg b/deluge/plugins/Scheduler/deluge_scheduler/data/green.svg new file mode 100644 index 0000000..ff3f5d6 --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/data/green.svg @@ -0,0 +1 @@ + diff --git a/deluge/plugins/Scheduler/deluge_scheduler/data/red.svg b/deluge/plugins/Scheduler/deluge_scheduler/data/red.svg new file mode 100644 index 0000000..ccb0822 --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/data/red.svg @@ -0,0 +1 @@ + diff --git a/deluge/plugins/Scheduler/deluge_scheduler/data/scheduler.js b/deluge/plugins/Scheduler/deluge_scheduler/data/scheduler.js new file mode 100644 index 0000000..f59068c --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/data/scheduler.js @@ -0,0 +1,621 @@ +/** + * scheduler.js + * The client-side javascript code for the Scheduler plugin. + * + * Copyright (C) samuel337 2011 + * + * 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'); + +Deluge.ux.ScheduleSelector = Ext.extend(Ext.form.FieldSet, { + title: _('Schedule'), + autoHeight: true, + style: 'margin-bottom: 0px; padding-bottom: 0px;', + border: false, + + states: [ + { + name: 'Normal', + backgroundColor: 'LightGreen', + borderColor: 'DarkGreen', + value: 0, + }, + { + name: 'Throttled', + backgroundColor: 'Yellow', + borderColor: 'Gold', + value: 1, + }, + { + name: 'Paused', + backgroundColor: 'OrangeRed', + borderColor: 'FireBrick', + value: 2, + }, + ], + daysOfWeek: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + + initComponent: function () { + Deluge.ux.ScheduleSelector.superclass.initComponent.call(this); + + // ExtJS' radiogroup implementation is very broken for styling. + /*this.stateBrush = this.add({ + xtype: 'radiogroup', + fieldLabel: _('State Brush'), + name: 'current_state_brush', + submitValue: false, + items: [ + { boxLabel: 'Normal', name: 'current_state_brush', inputValue: 0 }, + { boxLabel: 'Throttled', name: 'current_state_brush', inputValue: 1, checked: true }, + { boxLabel: 'Paused', name: 'current_state_brush', inputValue: 2 }, + ] + });*/ + }, + + onRender: function (ct, position) { + Deluge.ux.ScheduleSelector.superclass.onRender.call(this, ct, position); + + var dom = this.body.dom; + + function createEl(parent, type) { + var el = document.createElement(type); + parent.appendChild(el); + return el; + } + + // create state brushes + // tack a random number to the end to avoid clashes + this.stateBrushName = + 'schedule-state-brush-' + Math.round(Math.random() * 10000); + + var el1 = createEl(dom, 'div'); + + var el2 = createEl(el1, 'div'); + this.stateBrush = el2; + el2.id = this.stateBrushName; + + // for webkit + var floatAttr = 'float'; + if (el2.style.float == undefined) { + // for firefox + if (el2.style.cssFloat != undefined) floatAttr = 'cssFloat'; + // for IE + if (el2.style.styleFloat != undefined) floatAttr = 'styleFloat'; + } + el2.style[floatAttr] = 'right'; + + for (var i = 0; i < this.states.length; i++) { + var el3 = createEl(el2, 'input'); + el3.type = 'radio'; + el3.value = this.states[i].value; + el3.name = this.stateBrushName; + el3.id = this.stateBrushName + '-' + this.states[i].name; + + // isn't the first one + if (i > 0) el3.style.marginLeft = '7px'; + + // assume the first is the default state, so make the 2nd one the default brush + if (i == 1) el3.checked = true; + + var el4 = createEl(el2, 'label'); + el4.appendChild(document.createTextNode(this.states[i].name)); + el4.htmlFor = el3.id; + el4.style.backgroundColor = this.states[i].backgroundColor; + el4.style.borderBottom = '2px solid ' + this.states[i].borderColor; + el4.style.padding = '2px 3px'; + el4.style.marginLeft = '3px'; + } + + el1.appendChild(document.createTextNode('Select a state brush:')); + + el1.style.marginBottom = '10px'; + + // keep the radio buttons separate from the time bars + createEl(dom, 'div').style.clear = 'both'; + + var table = createEl(dom, 'table'); + table.cellSpacing = 0; + + // cache access to cells for easier access later + this.scheduleCells = {}; + + Ext.each( + this.daysOfWeek, + function (day) { + var cells = []; + var row = createEl(table, 'tr'); + var label = createEl(row, 'th'); + label.setAttribute( + 'style', + 'font-weight: bold; padding-right: 5px;' + ); + label.appendChild(document.createTextNode(day)); + for (var hour = 0; hour < 24; hour++) { + var cell = createEl(row, 'td'); + + // assume the first state is the default state + cell.currentValue = cell.oldValue = this.states[0].value; + cell.day = day; + cell.hour = hour; + + cell.width = '16px'; + cell.height = '20px'; + + cell.style.border = '1px solid #999999'; + // don't repeat borders in between cells + if (hour != 23) + // not the last cell + cell.style.borderRight = 'none'; + + this.updateCell(cell); + + cells.push(cell); + + cell = Ext.get(cell); + cell.on('click', this.onCellClick, this); + cell.on('mouseover', this.onCellMouseOver, this); + cell.on('mouseout', this.onCellMouseOut, this); + cell.on('mousedown', this.onCellMouseDown, this); + cell.on('mouseup', this.onCellMouseUp, this); + } + + // insert gap row to provide visual separation + row = createEl(table, 'tr'); + // blank cell to create gap + createEl(row, 'td').height = '3px'; + + this.scheduleCells[day] = cells; + }, + this + ); + }, + + updateCell: function (cell) { + // sanity check + if (cell.currentValue == undefined) return; + + for (var i in this.states) { + var curState = this.states[i]; + if (curState.value == cell.currentValue) { + cell.style.background = curState.backgroundColor; + break; + } + } + }, + + getCurrentBrushValue: function () { + var v = null; + var brushes = Ext.get(this.body.dom).findParent('form').elements[ + this.stateBrushName + ]; + Ext.each(brushes, function (b) { + if (b.checked) v = b.value; + }); + + return v; + }, + + onCellClick: function (event, cell) { + cell.oldValue = cell.currentValue; + + this.dragAnchor = null; + }, + + onCellMouseDown: function (event, cell) { + this.dragAnchor = cell; + }, + + onCellMouseUp: function (event, cell) { + // if we're dragging... + if (this.dragAnchor) { + // set all those between here and the anchor to the new values + if (cell.hour > this.dragAnchor.hour) + this.confirmCells(cell.day, this.dragAnchor.hour, cell.hour); + else if (cell.hour < this.dragAnchor.hour) + this.confirmCells(cell.day, cell.hour, this.dragAnchor.hour); + else this.confirmCells(cell.day, cell.hour, cell.hour); + + this.hideCellLeftTooltip(); + this.hideCellRightTooltip(); + this.dragAnchor = null; + } + }, + + onCellMouseOver: function (event, cell) { + // LEFT TOOL TIP + // if it isn't showing and we're dragging, show it. + // otherwise if dragging, leave it alone unless we're dragging to the left. + // if we're not dragging, show it. + var leftTooltipCell = null; + if (!this.dragAnchor) leftTooltipCell = cell; + else if ( + (this.dragAnchor && this.isCellLeftTooltipHidden()) || + (this.dragAnchor && this.dragAnchor.hour > cell.hour) + ) + leftTooltipCell = this.dragAnchor; + + if (leftTooltipCell) { + var hour = leftTooltipCell.hour; + var pm = false; + + // convert to 12-hour time + if (hour >= 12) { + pm = true; + if (hour > 12) hour -= 12; + } else if (hour == 0) { + // change 0 hour to 12am + hour = 12; + } + this.showCellLeftTooltip( + hour + ' ' + (pm ? 'pm' : 'am'), + leftTooltipCell + ); + } + + // RIGHT TOOL TIP + var rightTooltipCell = null; + if (this.dragAnchor) { + if (this.dragAnchor.hour == cell.hour) this.hideCellRightTooltip(); + else if ( + this.dragAnchor.hour > cell.hour && + this.isCellRightTooltipHidden() + ) + rightTooltipCell = this.dragAnchor; + // cell.hour > this.dragAnchor.hour + else rightTooltipCell = cell; + } + + if (rightTooltipCell) { + var hour = rightTooltipCell.hour; + var pm = false; + + // convert to 12-hour time + if (hour >= 12) { + pm = true; + if (hour > 12) hour -= 12; + } else if (hour == 0) { + // change 0 hour to 12am + hour = 12; + } + this.showCellRightTooltip( + hour + ' ' + (pm ? 'pm' : 'am'), + rightTooltipCell + ); + } + + // preview colour change and + // revert state for all those on the outer side of the drag if dragging + if (this.dragAnchor) { + if (cell.day != this.dragAnchor.day) { + // dragged into another day. Abort! Abort! + Ext.each( + this.daysOfWeek, + function (day) { + this.revertCells(day, 0, 23); + }, + this + ); + this.dragAnchor = null; + this.hideCellLeftTooltip(); + this.hideCellRightTooltip(); + } else if (cell.hour > this.dragAnchor.hour) { + // dragging right + this.revertCells(cell.day, cell.hour + 1, 23); + this.previewCells(cell.day, this.dragAnchor.hour, cell.hour); + } else if (cell.hour < this.dragAnchor.hour) { + // dragging left + this.revertCells(cell.day, 0, cell.hour - 1); + this.previewCells(cell.day, cell.hour, this.dragAnchor.hour); + } else { + // back to anchor cell + // don't know if it is from right or left, so revert all except this + this.revertCells(cell.day, cell.hour + 1, 23); + this.revertCells(cell.day, 0, cell.hour - 1); + } + } else { + // not dragging, just preview this cell + this.previewCells(cell.day, cell.hour, cell.hour); + } + }, + + onCellMouseOut: function (event, cell) { + if (!this.dragAnchor) this.hideCellLeftTooltip(); + + // revert state. If new state has been set, old and new will be equal. + // if dragging, this will be handled by the next mouse over + if (this.dragAnchor == null && cell.oldValue != cell.currentValue) { + this.revertCells(cell.day, cell.hour, cell.hour); + } + }, + + previewCells: function (day, fromHour, toHour) { + var cells = this.scheduleCells[day]; + var curBrushValue = this.getCurrentBrushValue(); + + if (toHour > cells.length) toHour = cells.length; + + for (var i = fromHour; i <= toHour; i++) { + if (cells[i].currentValue != curBrushValue) { + cells[i].oldValue = cells[i].currentValue; + cells[i].currentValue = curBrushValue; + this.updateCell(cells[i]); + } + } + }, + + revertCells: function (day, fromHour, toHour) { + var cells = this.scheduleCells[day]; + + if (toHour > cells.length) toHour = cells.length; + + for (var i = fromHour; i <= toHour; i++) { + cells[i].currentValue = cells[i].oldValue; + this.updateCell(cells[i]); + } + }, + + confirmCells: function (day, fromHour, toHour) { + var cells = this.scheduleCells[day]; + + if (toHour > cells.length) toHour = cells.length; + + for (var i = fromHour; i <= toHour; i++) { + if (cells[i].currentValue != cells[i].oldValue) { + cells[i].oldValue = cells[i].currentValue; + } + } + }, + + showCellLeftTooltip: function (text, cell) { + var tooltip = this.cellLeftTooltip; + + if (!tooltip) { + // no cached left tooltip exists, create one + tooltip = document.createElement('div'); + this.cellLeftTooltip = tooltip; + this.body.dom.appendChild(tooltip); + tooltip.style.position = 'absolute'; + tooltip.style.backgroundColor = '#F2F2F2'; + tooltip.style.border = '1px solid #333333'; + tooltip.style.padding = '1px 3px'; + tooltip.style.opacity = 0.8; + } + + // remove all existing children + while (tooltip.childNodes.length > 0) { + tooltip.removeChild(tooltip.firstChild); + } + // add the requested text + tooltip.appendChild(document.createTextNode(text)); + + // place the tooltip + Ext.get(tooltip).alignTo(cell, 'br-tr'); + + // make it visible + tooltip.style.visibility = 'visible'; + }, + + hideCellLeftTooltip: function () { + if (this.cellLeftTooltip) { + this.cellLeftTooltip.style.visibility = 'hidden'; + } + }, + + isCellLeftTooltipHidden: function () { + if (this.cellLeftTooltip) + return this.cellLeftTooltip.style.visibility == 'hidden'; + else return true; + }, + + showCellRightTooltip: function (text, cell) { + var tooltip = this.cellRightTooltip; + + if (!tooltip) { + // no cached left tooltip exists, create one + tooltip = document.createElement('div'); + this.cellRightTooltip = tooltip; + this.body.dom.appendChild(tooltip); + tooltip.style.position = 'absolute'; + tooltip.style.backgroundColor = '#F2F2F2'; + tooltip.style.border = '1px solid #333333'; + tooltip.style.padding = '1px 3px'; + tooltip.style.opacity = 0.8; + } + + // remove all existing children + while (tooltip.childNodes.length > 0) { + tooltip.removeChild(tooltip.firstChild); + } + // add the requested text + tooltip.appendChild(document.createTextNode(text)); + + // place the tooltip + Ext.get(tooltip).alignTo(cell, 'bl-tl'); + + // make it visible + tooltip.style.visibility = 'visible'; + }, + + hideCellRightTooltip: function () { + if (this.cellRightTooltip) { + this.cellRightTooltip.style.visibility = 'hidden'; + } + }, + + isCellRightTooltipHidden: function () { + if (this.cellRightTooltip) + return this.cellRightTooltip.style.visibility == 'hidden'; + else return true; + }, + + getConfig: function () { + var config = []; + + for (var i = 0; i < 24; i++) { + var hourConfig = [0, 0, 0, 0, 0, 0, 0]; + + for (var j = 0; j < this.daysOfWeek.length; j++) { + hourConfig[j] = parseInt( + this.scheduleCells[this.daysOfWeek[j]][i].currentValue + ); + } + + config.push(hourConfig); + } + + return config; + }, + + setConfig: function (config) { + for (var i = 0; i < 24; i++) { + var hourConfig = config[i]; + + for (var j = 0; j < this.daysOfWeek.length; j++) { + if (this.scheduleCells == undefined) { + var cell = hourConfig[j]; + } else { + var cell = this.scheduleCells[this.daysOfWeek[j]][i]; + } + cell.currentValue = cell.oldValue = hourConfig[j]; + this.updateCell(cell); + } + } + }, +}); + +Ext.ns('Deluge.ux.preferences'); + +Deluge.ux.preferences.SchedulerPage = Ext.extend(Ext.Panel, { + border: false, + title: _('Scheduler'), + header: false, + layout: 'fit', + + initComponent: function () { + Deluge.ux.preferences.SchedulerPage.superclass.initComponent.call(this); + + this.form = this.add({ + xtype: 'form', + layout: 'form', + border: false, + autoHeight: true, + }); + + this.schedule = this.form.add(new Deluge.ux.ScheduleSelector()); + + this.slowSettings = this.form.add({ + xtype: 'fieldset', + border: false, + title: _('Throttled Settings'), + autoHeight: true, + defaultType: 'spinnerfield', + defaults: { + minValue: -1, + maxValue: 99999, + }, + style: 'margin-top: 5px; margin-bottom: 0px; padding-bottom: 0px;', + labelWidth: 200, + }); + + this.downloadLimit = this.slowSettings.add({ + fieldLabel: _('Maximum Download Speed (KiB/s)'), + name: 'download_limit', + width: 80, + value: -1, + decimalPrecision: 0, + }); + this.uploadLimit = this.slowSettings.add({ + fieldLabel: _('Maximum Upload Speed (KiB/s)'), + name: 'upload_limit', + width: 80, + value: -1, + decimalPrecision: 0, + }); + this.activeTorrents = this.slowSettings.add({ + fieldLabel: _('Active Torrents'), + name: 'active_torrents', + width: 80, + value: -1, + decimalPrecision: 0, + }); + this.activeDownloading = this.slowSettings.add({ + fieldLabel: _('Active Downloading'), + name: 'active_downloading', + width: 80, + value: -1, + decimalPrecision: 0, + }); + this.activeSeeding = this.slowSettings.add({ + fieldLabel: _('Active Seeding'), + name: 'active_seeding', + width: 80, + value: -1, + decimalPrecision: 0, + }); + + this.on('show', this.updateConfig, this); + }, + + onRender: function (ct, position) { + Deluge.ux.preferences.SchedulerPage.superclass.onRender.call( + this, + ct, + position + ); + this.form.layout = new Ext.layout.FormLayout(); + this.form.layout.setContainer(this); + this.form.doLayout(); + }, + + onApply: function () { + // build settings object + var config = {}; + + config['button_state'] = this.schedule.getConfig(); + config['low_down'] = this.downloadLimit.getValue(); + config['low_up'] = this.uploadLimit.getValue(); + config['low_active'] = this.activeTorrents.getValue(); + config['low_active_down'] = this.activeDownloading.getValue(); + config['low_active_up'] = this.activeSeeding.getValue(); + + deluge.client.scheduler.set_config(config); + }, + + onOk: function () { + this.onApply(); + }, + + updateConfig: function () { + deluge.client.scheduler.get_config({ + success: function (config) { + this.schedule.setConfig(config['button_state']); + this.downloadLimit.setValue(config['low_down']); + this.uploadLimit.setValue(config['low_up']); + this.activeTorrents.setValue(config['low_active']); + this.activeDownloading.setValue(config['low_active_down']); + this.activeSeeding.setValue(config['low_active_up']); + }, + scope: this, + }); + }, +}); + +Deluge.plugins.SchedulerPlugin = Ext.extend(Deluge.Plugin, { + name: 'Scheduler', + + onDisable: function () { + deluge.preferences.removePage(this.prefsPage); + }, + + onEnable: function () { + this.prefsPage = deluge.preferences.addPage( + new Deluge.ux.preferences.SchedulerPage() + ); + }, +}); +Deluge.registerPlugin('Scheduler', Deluge.plugins.SchedulerPlugin); diff --git a/deluge/plugins/Scheduler/deluge_scheduler/data/yellow.svg b/deluge/plugins/Scheduler/deluge_scheduler/data/yellow.svg new file mode 100644 index 0000000..8881a8c --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/data/yellow.svg @@ -0,0 +1 @@ + diff --git a/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py b/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py new file mode 100644 index 0000000..16222c8 --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py @@ -0,0 +1,356 @@ +# +# Copyright (C) 2009 Andrew Resch +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +import logging + +from gi.repository import Gdk, Gtk + +import deluge.component as component +from deluge.plugins.pluginbase import Gtk3PluginBase +from deluge.ui.client import client + +from .common import get_resource + +log = logging.getLogger(__name__) + +DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] + + +class SchedulerSelectWidget(Gtk.DrawingArea): + def __init__(self, hover): + super().__init__() + self.set_events( + Gdk.EventMask.BUTTON_PRESS_MASK + | Gdk.EventMask.BUTTON_RELEASE_MASK + | Gdk.EventMask.POINTER_MOTION_MASK + | Gdk.EventMask.LEAVE_NOTIFY_MASK + ) + + self.connect('draw', self.draw) + self.connect('button_press_event', self.mouse_down) + self.connect('button_release_event', self.mouse_up) + self.connect('motion_notify_event', self.mouse_hover) + self.connect('leave_notify_event', self.mouse_leave) + + self.colors = [ + [115 / 255, 210 / 255, 22 / 255], + [237 / 255, 212 / 255, 0 / 255], + [204 / 255, 0 / 255, 0 / 255], + ] + self.button_state = [[0] * 7 for dummy in range(24)] + + self.start_point = [0, 0] + self.hover_point = [-1, -1] + self.hover_label = hover + self.hover_days = DAYS + self.mouse_press = False + self.set_size_request(350, 150) + + def set_button_state(self, state): + self.button_state = [] + for s in state: + self.button_state.append(list(s)) + log.debug(self.button_state) + + # redraw the whole thing + def draw(self, widget, context): + width = widget.get_allocated_width() + height = widget.get_allocated_height() + context.rectangle(0, 0, width, height) + context.clip() + + for y in range(7): + for x in range(24): + context.set_source_rgba( + self.colors[self.button_state[x][y]][0], + self.colors[self.button_state[x][y]][1], + self.colors[self.button_state[x][y]][2], + 0.5, + ) + context.rectangle( + width * (6 * x / 145 + 1 / 145), + height * (6 * y / 43 + 1 / 43), + 6 * width / 145, + 5 * height / 43, + ) + context.fill_preserve() + context.set_source_rgba(0, 0, 0, 0.7) + context.set_line_width(1) + context.stroke() + + # coordinates --> which box + def get_point(self, event): + width = self.get_allocated_width() + height = self.get_allocated_height() + x = int((event.x - width * 0.5 / 145) / (6 * width / 145)) + y = int((event.y - height * 0.5 / 43) / (6 * height / 43)) + + if x > 23: + x = 23 + elif x < 0: + x = 0 + if y > 6: + y = 6 + elif y < 0: + y = 0 + + return [x, y] + + # mouse down + def mouse_down(self, widget, event): + self.mouse_press = True + self.start_point = self.get_point(event) + + # if the same box -> change it + def mouse_up(self, widget, event): + self.mouse_press = False + end_point = self.get_point(event) + + # change color on mouseclick depending on the button + if end_point[0] is self.start_point[0] and end_point[1] is self.start_point[1]: + if event.button == 1: + self.button_state[end_point[0]][end_point[1]] += 1 + if self.button_state[end_point[0]][end_point[1]] > 2: + self.button_state[end_point[0]][end_point[1]] = 0 + elif event.button == 3: + self.button_state[end_point[0]][end_point[1]] -= 1 + if self.button_state[end_point[0]][end_point[1]] < 0: + self.button_state[end_point[0]][end_point[1]] = 2 + self.queue_draw() + + # if box changed and mouse is pressed draw all boxes from start point to end point + # set hover text etc.. + def mouse_hover(self, widget, event): + if self.get_point(event) != self.hover_point: + self.hover_point = self.get_point(event) + + self.hover_label.set_text( + self.hover_days[self.hover_point[1]] + + ' ' + + str(self.hover_point[0]) + + ':00 - ' + + str(self.hover_point[0]) + + ':59' + ) + + if self.mouse_press: + points = [ + [self.hover_point[0], self.start_point[0]], + [self.hover_point[1], self.start_point[1]], + ] + + for x in range(min(points[0]), max(points[0]) + 1): + for y in range(min(points[1]), max(points[1]) + 1): + self.button_state[x][y] = self.button_state[ + self.start_point[0] + ][self.start_point[1]] + + self.queue_draw() + + # clear hover text on mouse leave + def mouse_leave(self, widget, event): + self.hover_label.set_text('') + self.hover_point = [-1, -1] + + +class GtkUI(Gtk3PluginBase): + def enable(self): + self.create_prefs_page() + + component.get('PluginManager').register_hook( + 'on_apply_prefs', self.on_apply_prefs + ) + component.get('PluginManager').register_hook( + 'on_show_prefs', self.on_show_prefs + ) + self.statusbar = component.get('StatusBar') + self.status_item = self.statusbar.add_item( + image=get_resource('green.svg'), + text='', + callback=self.on_status_item_clicked, + tooltip='Scheduler', + ) + + def on_state_deferred(state): + self.state = state + self.on_scheduler_event(state) + + self.on_show_prefs() + + client.scheduler.get_state().addCallback(on_state_deferred) + client.register_event_handler('SchedulerEvent', self.on_scheduler_event) + + def disable(self): + component.get('Preferences').remove_page(_('Scheduler')) + # Reset statusbar dict. + self.statusbar.config_value_changed_dict[ + 'max_download_speed' + ] = self.statusbar._on_max_download_speed + self.statusbar.config_value_changed_dict[ + 'max_upload_speed' + ] = self.statusbar._on_max_upload_speed + # Remove statusbar item. + self.statusbar.remove_item(self.status_item) + del self.status_item + + 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 on_apply_prefs(self): + log.debug('applying prefs for Scheduler') + config = {} + config['low_down'] = self.spin_download.get_value() + config['low_up'] = self.spin_upload.get_value() + config['low_active'] = self.spin_active.get_value_as_int() + config['low_active_down'] = self.spin_active_down.get_value_as_int() + config['low_active_up'] = self.spin_active_up.get_value_as_int() + config['button_state'] = self.scheduler_select.button_state + client.scheduler.set_config(config) + + def on_show_prefs(self): + def on_get_config(config): + log.debug('config: %s', config) + self.scheduler_select.set_button_state(config['button_state']) + self.spin_download.set_value(config['low_down']) + self.spin_upload.set_value(config['low_up']) + self.spin_active.set_value(config['low_active']) + self.spin_active_down.set_value(config['low_active_down']) + self.spin_active_up.set_value(config['low_active_up']) + + client.scheduler.get_config().addCallback(on_get_config) + + def on_scheduler_event(self, state): + self.state = state + self.status_item.set_image_from_file(get_resource(self.state.lower() + '.svg')) + if self.state == 'Yellow': + # Prevent func calls in Statusbar if the config changes. + self.statusbar.config_value_changed_dict.pop('max_download_speed', None) + self.statusbar.config_value_changed_dict.pop('max_upload_speed', None) + try: + self.statusbar._on_max_download_speed(self.spin_download.get_value()) + self.statusbar._on_max_upload_speed(self.spin_upload.get_value()) + except AttributeError: + # Skip error due to Plugin being enabled before statusbar items created on startup. + pass + else: + self.statusbar.config_value_changed_dict[ + 'max_download_speed' + ] = self.statusbar._on_max_download_speed + self.statusbar.config_value_changed_dict[ + 'max_upload_speed' + ] = self.statusbar._on_max_upload_speed + + def update_config_values(config): + try: + self.statusbar._on_max_download_speed(config['max_download_speed']) + self.statusbar._on_max_upload_speed(config['max_upload_speed']) + except AttributeError: + # Skip error due to Plugin being enabled before statusbar items created on startup. + pass + + client.core.get_config_values( + ['max_download_speed', 'max_upload_speed'] + ).addCallback(update_config_values) + + def on_status_item_clicked(self, widget, event): + component.get('Preferences').show('Scheduler') + + # Configuration dialog + def create_prefs_page(self): + # Select Widget + hover = Gtk.Label() + self.scheduler_select = SchedulerSelectWidget(hover) + + vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=5) + hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=5) + vbox_days = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=0) + for day in DAYS: + vbox_days.pack_start(Gtk.Label(day, xalign=0), True, False, 0) + hbox.pack_start(vbox_days, False, False, 15) + hbox.pack_start(self.scheduler_select, True, True, 0) + frame = Gtk.Frame() + label = Gtk.Label() + label.set_markup(_('Schedule')) + frame.set_label_widget(label) + frame.set_shadow_type(Gtk.ShadowType.NONE) + frame.set_margin_left(15) + frame.add(hbox) + + vbox.pack_start(frame, False, False, 0) + vbox.pack_start(hover, False, False, 0) + + table = Gtk.Table(5, 2) + table.set_margin_left(15) + + label = Gtk.Label(_('Download Limit:')) + label.set_alignment(0.0, 0.6) + table.attach_defaults(label, 0, 1, 0, 1) + self.spin_download = Gtk.SpinButton() + self.spin_download.set_numeric(True) + self.spin_download.set_range(-1.0, 99999.0) + self.spin_download.set_increments(1, 10) + table.attach_defaults(self.spin_download, 1, 2, 0, 1) + + label = Gtk.Label(_('Upload Limit:')) + label.set_alignment(0.0, 0.6) + table.attach_defaults(label, 0, 1, 1, 2) + self.spin_upload = Gtk.SpinButton() + self.spin_upload.set_numeric(True) + self.spin_upload.set_range(-1.0, 99999.0) + self.spin_upload.set_increments(1, 10) + table.attach_defaults(self.spin_upload, 1, 2, 1, 2) + + label = Gtk.Label(_('Active Torrents:')) + label.set_alignment(0.0, 0.6) + table.attach_defaults(label, 0, 1, 2, 3) + self.spin_active = Gtk.SpinButton() + self.spin_active.set_numeric(True) + self.spin_active.set_range(-1, 9999) + self.spin_active.set_increments(1, 10) + table.attach_defaults(self.spin_active, 1, 2, 2, 3) + + label = Gtk.Label(_('Active Downloading:')) + label.set_alignment(0.0, 0.6) + table.attach_defaults(label, 0, 1, 3, 4) + self.spin_active_down = Gtk.SpinButton() + self.spin_active_down.set_numeric(True) + self.spin_active_down.set_range(-1, 9999) + self.spin_active_down.set_increments(1, 10) + table.attach_defaults(self.spin_active_down, 1, 2, 3, 4) + + label = Gtk.Label(_('Active Seeding:')) + label.set_alignment(0.0, 0.6) + table.attach_defaults(label, 0, 1, 4, 5) + self.spin_active_up = Gtk.SpinButton() + self.spin_active_up.set_numeric(True) + self.spin_active_up.set_range(-1, 9999) + self.spin_active_up.set_increments(1, 10) + table.attach_defaults(self.spin_active_up, 1, 2, 4, 5) + + eventbox = Gtk.EventBox() + eventbox.add(table) + frame = Gtk.Frame() + label = Gtk.Label() + label.set_markup(_('Slow Settings')) + label.modify_bg(Gtk.StateFlags.NORMAL, Gdk.color_parse('#EDD400')) + frame.set_label_widget(label) + frame.set_margin_left(15) + frame.set_border_width(2) + frame.add(eventbox) + vbox.pack_start(frame, False, False, 0) + + vbox.show_all() + component.get('Preferences').add_page(_('Scheduler'), vbox) diff --git a/deluge/plugins/Scheduler/deluge_scheduler/webui.py b/deluge/plugins/Scheduler/deluge_scheduler/webui.py new file mode 100644 index 0000000..e417916 --- /dev/null +++ b/deluge/plugins/Scheduler/deluge_scheduler/webui.py @@ -0,0 +1,23 @@ +# +# Copyright (C) 2009 Andrew Resch +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# +import logging + +from deluge.plugins.pluginbase import WebPluginBase + +from .common import get_resource + +log = logging.getLogger(__name__) + + +class WebUI(WebPluginBase): + scripts = [get_resource('scheduler.js')] + debug_scripts = scripts diff --git a/deluge/plugins/Scheduler/setup.py b/deluge/plugins/Scheduler/setup.py new file mode 100644 index 0000000..3ac181d --- /dev/null +++ b/deluge/plugins/Scheduler/setup.py @@ -0,0 +1,45 @@ +# +# Copyright (C) 2009 Andrew Resch +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +from setuptools import find_packages, setup + +__plugin_name__ = 'Scheduler' +__author__ = 'Andrew Resch' +__author_email__ = 'andrewresch@gmail.com' +__version__ = '0.3' +__url__ = 'http://deluge-torrent.org' +__license__ = 'GPLv3' +__description__ = 'Schedule limits on a per-hour per-day basis.' +__long_description__ = """""" +__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__ 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:GtkUIPlugin + [deluge.plugin.web] + %s = deluge_%s:WebUIPlugin + """ + % ((__plugin_name__, __plugin_name__.lower()) * 3), +) -- cgit v1.2.3