diff options
Diffstat (limited to 'deluge/scripts')
-rwxr-xr-x | deluge/scripts/create_icons.py | 201 | ||||
-rw-r--r-- | deluge/scripts/create_plugin.py | 415 | ||||
-rw-r--r-- | deluge/scripts/deluge_remote.py | 138 |
3 files changed, 754 insertions, 0 deletions
diff --git a/deluge/scripts/create_icons.py b/deluge/scripts/create_icons.py new file mode 100755 index 0000000..479505c --- /dev/null +++ b/deluge/scripts/create_icons.py @@ -0,0 +1,201 @@ +#!/usr/bin/python3 +# +# Create Deluge PNG icons from SVG +# +# Required image tools: +# * rsvg-convert +# * convert (ImageMagik) +# * oxipng +# * pngquant +# +import shutil +import subprocess +from dataclasses import dataclass, field +from pathlib import Path + + +@dataclass +class IconPack: + name: str + dir: Path + icon_sizes: list[int] + panel_sizes: list[int] + ico_sizes: list[int] + pixmaps_dir: Path = field(init=False) + theme_dir: Path = field(init=False) + theme_svg: Path = field(init=False) + theme_pngs: dict[int, Path] = field(init=False) + logo_svg: Path = field(init=False) + logo_ico: Path = field(init=False) + logo_png: Path = field(init=False) + + def __post_init__(self): + self.pixmaps_dir = self.dir / 'pixmaps' + self.logo_svg = self.pixmaps_dir / f'{self.name}.svg' + self.logo_ico = self.pixmaps_dir / f'{self.name}.ico' + self.logo_png = self.pixmaps_dir / f'{self.name}.png' + + self.theme_dir = self.dir / 'icons' / 'hicolor' + self.theme_svg = self.theme_dir / 'scalable' / 'apps' / f'{self.name}.svg' + self.theme_pngs = self.create_theme_pngs_paths( + self.name, self.icon_sizes, self.theme_dir + ) + + @staticmethod + def create_theme_pngs_paths(name, icon_sizes, out_dir): + return { + size: out_dir / f'{size}x{size}' / 'apps' / f'{name}.png' + for size in icon_sizes + } + + +@dataclass +class WebIconPack: + name: str + dir: Path + icon_sizes: list[int] + favicon_sizes: list[int] + icons_dir: Path = field(init=False) + touch: Path = field(init=False) + favicon: Path = field(init=False) + + def __post_init__(self): + self.icons_dir = self.dir / 'icons' + self.touch = self.icons_dir / f'{self.name}-apple-180.png' + self.favicon = self.icons_dir / 'favicon.ico' + + +def convert_svg_to_png(svg_file, png_file, size, background_color=None): + rsvg_options = [ + '-w', + str(size), + '-h', + str(size), + '-o', + png_file, + ] + rsvg_options + ['-b', {background_color}] if background_color else [] + + subprocess.run(['rsvg-convert'] + rsvg_options + [svg_file], check=True) + + +def compress_png(png_file): + subprocess.run( + ['pngquant', '--quality=70-95', '--ext', '.png', '--force', png_file], + check=True, + ) + subprocess.run(['oxipng', png_file], check=True) + + +def create_panel_icons(icon_pack, sizes): + for size in sizes: + app_png = icon_pack[size] + panel_png = app_png.with_name(f'{app_png.stem}-panel.png') + shutil.copyfile(app_png, panel_png) + + +def create_hicolor_icons(svg_icon, icon_pack): + """Convert SVG icon to hicolor PNG icons.""" + for size, png_file in icon_pack.items(): + png_file.parent.mkdir(parents=True, exist_ok=True) + convert_svg_to_png(svg_icon, png_file, size) + compress_png(png_file) + + +def create_ico_icon(icon_pack, sizes, ico_file): + infiles = [icon_pack[size] for size in sizes] + ico_file.parent.mkdir(parents=True, exist_ok=True) + + subprocess.run(['convert', *infiles, ico_file], check=True) + + +def create_hicolor_svg(src_svg, dest_svg): + dest_svg.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(src_svg, dest_svg) + + +def create_mini_icons(pixmaps_dir): + pixmap_svgs = pixmaps_dir.glob('*.svg') + + for svg_file in pixmap_svgs: + png_file = pixmaps_dir / f'{svg_file.stem}16.png' + convert_svg_to_png(svg_file, png_file, 16) + compress_png(png_file) + + +def create_logo(deluge_png, pixmap_png): + pixmap_png.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(deluge_png, pixmap_png) + + +def create_web_status_icons(src_dir: Path, dest_dir: Path): + """Web UI status icons from 16px icons.""" + pngs_16px = src_dir.glob('*16.png') + dest_dir.mkdir(parents=True, exist_ok=True) + for path in pngs_16px: + if path.stem.startswith('tracker'): + continue + new_name = path.stem.replace('16', '') + '.png' + shutil.copyfile(path, dest_dir / new_name) + + +def create_touch_icon(svg_file, png_file, size): + """Web icons with background color for Apple or Android""" + png_file.parent.mkdir(parents=True, exist_ok=True) + convert_svg_to_png(svg_file, png_file, size, background_color='#599EEE') + compress_png(png_file) + + +def create_web_icons(app_pngs, sizes, dest_dir): + dest_dir.mkdir(parents=True, exist_ok=True) + for size in sizes: + app_png = app_pngs[size] + web_png = dest_dir / f'{app_png.stem}-{size}.png' + shutil.copyfile(app_png, web_png) + + +def main(): + data_dir = Path.cwd() / 'deluge' / 'ui' / 'data' + if not data_dir.is_dir(): + exit(f'No path to UI data dir: {data_dir}') + + # Create Deluge UI icons + icon_pack_sizes = [16, 22, 24, 32, 36, 48, 64, 72, 96, 128, 192, 256, 512] + panel_icon_sizes = [16, 22, 24] + ico_icon_sizes = [16, 32, 48, 64, 128, 256] + ui_icons = IconPack( + name='deluge', + dir=data_dir, + icon_sizes=icon_pack_sizes, + panel_sizes=panel_icon_sizes, + ico_sizes=ico_icon_sizes, + ) + + # Theme icons for GTK + create_hicolor_icons(ui_icons.logo_svg, ui_icons.theme_pngs) + create_hicolor_svg(ui_icons.logo_svg, ui_icons.theme_svg) + create_mini_icons(ui_icons.pixmaps_dir) + # Panel icon for systray + create_panel_icons(ui_icons.theme_pngs, ui_icons.panel_sizes) + + # Deluge logos + create_ico_icon(ui_icons.theme_pngs, ui_icons.ico_sizes, ui_icons.logo_ico) + create_logo(ui_icons.theme_pngs[48], ui_icons.logo_png) + + # Web UI Icons + web_icon_sizes = [32, 192, 512] + favicon_sizes = [16, 32, 48] + web_icons = WebIconPack( + name='deluge', + dir=data_dir / '..' / 'web', + icon_sizes=web_icon_sizes, + favicon_sizes=favicon_sizes, + ) + create_web_icons(ui_icons.theme_pngs, web_icons.icon_sizes, web_icons.icons_dir) + create_web_status_icons(ui_icons.pixmaps_dir, web_icons.icons_dir) + create_touch_icon(ui_icons.logo_svg, web_icons.touch, 180) + create_ico_icon(ui_icons.theme_pngs, web_icons.favicon_sizes, web_icons.favicon) + + +if __name__ == '__main__': + main() diff --git a/deluge/scripts/create_plugin.py b/deluge/scripts/create_plugin.py new file mode 100644 index 0000000..7383661 --- /dev/null +++ b/deluge/scripts/create_plugin.py @@ -0,0 +1,415 @@ +""" +Creates an empty plugin and links it from ~/.config/deluge/plugins +This plugin includes the framework for using the preferences dialog + +example: +python create_plugin.py --name MyPlugin2 --basepath . --author-name "Your Name" --author-email "yourname@example.com" + +""" + +import os +import sys +from argparse import ArgumentParser +from datetime import datetime + +import deluge.common + +parser = ArgumentParser() +parser.add_argument( + '-n', '--name', metavar='<plugin name>', required=True, help='Plugin name' +) +parser.add_argument('-m', '--module-name', metavar='<module name>', help='Module name') +parser.add_argument( + '-p', '--basepath', metavar='<path>', required=True, help='Base path' +) +parser.add_argument( + '-a', + '--author-name', + metavar='<author name>', + required=True, + help='Author name,for the GPL header', +) +parser.add_argument( + '-e', + '--author-email', + metavar='<author email>', + required=True, + help='Author email,for the GPL header', +) +parser.add_argument('-u', '--url', metavar='<URL>', help='Homepage URL') +parser.add_argument( + '-c', + '--config', + metavar='<Config dir>', + dest='configdir', + help='Location of deluge configuration', +) + +options = parser.parse_args() + + +def create_plugin(): + if not options.url: + options.url = '' + + if not os.path.exists(options.basepath): + print('basepath does not exist') + return + + if not options.configdir: + options.configdir = deluge.common.get_default_config_dir() + + options.configdir = os.path.realpath(options.configdir) + + real_name = options.name + name = real_name.replace(' ', '_') + safe_name = name.lower() + if options.module_name: + safe_name = options.module_name.lower() + plugin_base = os.path.realpath(os.path.join(options.basepath, name)) + src = os.path.join(plugin_base, 'deluge_' + safe_name) + data_dir = os.path.join(src, 'data') + python_path = sys.executable + + if os.path.exists(plugin_base): + print('the directory %s already exists, delete it first' % plugin_base) + return + + def write_file(path, filename, template, include_gpl=True): + plugin_args = { + 'author_name': options.author_name, + 'author_email': options.author_email, + 'name': name, + 'safe_name': safe_name, + 'filename': filename, + 'plugin_base': plugin_base, + 'python_path': python_path, + 'url': options.url, + 'configdir': options.configdir, + 'current_year': datetime.utcnow().year, + } + + filename = os.path.join(path, filename) + with open(filename, 'w') as _file: + if filename.endswith('.py') and include_gpl: + _file.write(GPL % plugin_args) + _file.write(template % plugin_args) + + print('creating folders..') + os.mkdir(plugin_base) + os.mkdir(src) + os.mkdir(data_dir) + + print('creating files..') + write_file(plugin_base, 'setup.py', SETUP) + write_file(src, '__init__.py', INIT) + write_file(src, 'gtk3ui.py', GTK3UI) + write_file(src, 'webui.py', WEBUI) + write_file(src, 'core.py', CORE) + write_file(src, 'common.py', COMMON) + write_file(data_dir, 'config.ui', GLADE) + write_file(data_dir, '%s.js' % safe_name, DEFAULT_JS) + + # add an input parameter for this? + print('building dev-link..') + if deluge.common.windows_check(): + write_file(plugin_base, 'create_dev_link.bat', CREATE_DEV_LINK_WIN) + dev_link_path = os.path.join(plugin_base, 'create_dev_link.bat') + else: + write_file(plugin_base, 'create_dev_link.sh', CREATE_DEV_LINK_NIX) + dev_link_path = os.path.join(plugin_base, 'create_dev_link.sh') + os.system('chmod +x %s' % dev_link_path) # lazy.. + os.system(dev_link_path) + + +CORE = """from __future__ import unicode_literals + +import logging + +import deluge.configmanager +from deluge.core.rpcserver import export +from deluge.plugins.pluginbase import CorePluginBase + +log = logging.getLogger(__name__) + +DEFAULT_PREFS = { + 'test': 'NiNiNi' +} + + +class Core(CorePluginBase): + def enable(self): + self.config = deluge.configmanager.ConfigManager( + '%(safe_name)s.conf', DEFAULT_PREFS) + + def disable(self): + pass + + def update(self): + pass + + @export + def set_config(self, config): + \"\"\"Sets the config dictionary\"\"\" + for key in config: + self.config[key] = config[key] + self.config.save() + + @export + def get_config(self): + \"\"\"Returns the config dictionary\"\"\" + return self.config.config +""" + +INIT = """from deluge.plugins.init import PluginInitBase + + +class CorePlugin(PluginInitBase): + def __init__(self, plugin_name): + from .core import Core as PluginClass + self._plugin_cls = PluginClass + super(CorePlugin, self).__init__(plugin_name) + + +class Gtk3UIPlugin(PluginInitBase): + def __init__(self, plugin_name): + from .gtk3ui import Gtk3UI as PluginClass + self._plugin_cls = PluginClass + super(Gtk3UIPlugin, self).__init__(plugin_name) + + +class WebUIPlugin(PluginInitBase): + def __init__(self, plugin_name): + from .webui import WebUI as PluginClass + self._plugin_cls = PluginClass + super(WebUIPlugin, self).__init__(plugin_name) +""" + + +SETUP = """from setuptools import find_packages, setup + +__plugin_name__ = '%(name)s' +__author__ = '%(author_name)s' +__author_email__ = '%(author_email)s' +__version__ = '0.1' +__url__ = '%(url)s' +__license__ = 'GPLv3' +__description__ = '' +__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__, + + packages=find_packages(), + package_data=__pkg_data__, + + entry_points=\"\"\" + [deluge.plugin.core] + %%s = deluge_%%s:CorePlugin + [deluge.plugin.gtk3ui] + %%s = deluge_%%s:Gtk3UIPlugin + [deluge.plugin.web] + %%s = deluge_%%s:WebUIPlugin + \"\"\" %% ((__plugin_name__, __plugin_name__.lower()) * 3) +) +""" + +COMMON = """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)) +""" + +GTK3UI = """from __future__ import unicode_literals + +import logging + +from gi.repository import 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__) + + +class Gtk3UI(Gtk3PluginBase): + def enable(self): + self.builder = Gtk.Builder() + self.builder.add_from_file(get_resource('config.ui')) + + component.get('Preferences').add_page( + '%(name)s', self.builder.get_object('prefs_box')) + component.get('PluginManager').register_hook( + 'on_apply_prefs', self.on_apply_prefs) + component.get('PluginManager').register_hook( + 'on_show_prefs', self.on_show_prefs) + + def disable(self): + component.get('Preferences').remove_page('%(name)s') + 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 %(name)s') + config = { + 'test': self.builder.get_object('txt_test').get_text() + } + client.%(safe_name)s.set_config(config) + + def on_show_prefs(self): + client.%(safe_name)s.get_config().addCallback(self.cb_get_config) + + def cb_get_config(self, config): + \"\"\"callback for on show_prefs\"\"\" + self.builder.get_object('txt_test').set_text(config['test']) +""" + +GLADE = """<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.18.3 --> +<interface> + <requires lib="gtk+" version="3.0"/> + <object class="GtkWindow" id="window1"> + <child> + <object class="GtkBox" id="prefs_box"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">Test config value:</property> + </object> + </child> + <child> + <object class="GtkEntry" id="txt_test"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> +</interface> +""" + +WEBUI = """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('%(safe_name)s.js')] + + def enable(self): + pass + + def disable(self): + pass +""" + +DEFAULT_JS = """/** + * Script: %(filename)s + * The client-side javascript code for the %(name)s plugin. + * + * Copyright: + * (C) %(author_name)s %(current_year)s <%(author_email)s> + * + * This file is part of %(name)s and is licensed under GNU GPL 3.0, or + * later, with the additional special exception to link portions of this + * program with the OpenSSL library. See LICENSE for more details. + */ + +%(name)sPlugin = Ext.extend(Deluge.Plugin, { + constructor: function(config) { + config = Ext.apply({ + name: '%(name)s' + }, config); + %(name)sPlugin.superclass.constructor.call(this, config); + }, + + onDisable: function() { + deluge.preferences.removePage(this.prefsPage); + }, + + onEnable: function() { + this.prefsPage = deluge.preferences.addPage( + new Deluge.ux.preferences.%(name)sPage()); + } +}); +new %(name)sPlugin(); +""" + +GPL = """# -*- coding: utf-8 -*- +# Copyright (C) %(current_year)d %(author_name)s <%(author_email)s> +# +# Basic plugin template created by the Deluge Team. +# +# This file is part of %(name)s and is licensed under GNU GPL 3.0, or later, +# with the additional special exception to link portions of this program with +# the OpenSSL library. See LICENSE for more details. +""" + +CREATE_DEV_LINK_NIX = """#!/bin/bash +BASEDIR=$(cd `dirname $0` && pwd) +CONFIG_DIR=$( test -z $1 && echo "%(configdir)s" || echo "$1") +[ -d "$CONFIG_DIR/plugins" ] || echo "Config dir \"$CONFIG_DIR\" is either not a directory \ +or is not a proper deluge config directory. Exiting" +[ -d "$CONFIG_DIR/plugins" ] || exit 1 +cd $BASEDIR +test -d $BASEDIR/temp || mkdir $BASEDIR/temp +export PYTHONPATH=$BASEDIR/temp +%(python_path)s setup.py build develop --install-dir $BASEDIR/temp +cp $BASEDIR/temp/*.egg-link $CONFIG_DIR/plugins +rm -fr $BASEDIR/temp +""" + +CREATE_DEV_LINK_WIN = """@echo off +set BASEDIR=%%~dp0 +set BASEDIR=%%BASEDIR:~0,-1%% +if [%%1]==[] ( + set CONFIG_DIR=%(configdir)s +) else ( + set CONFIG_DIR=%%1 +) +if not exist %%CONFIG_DIR%%\\plugins ( + echo Config dir %%CONFIG_DIR%% is either not a directory \ +or is not a proper deluge config directory. Exiting + exit /b 1 +) +cd %%BASEDIR%% +if not exist %%BASEDIR%%\\temp ( + md %%BASEDIR%%\\temp +) +set PYTHONPATH=%%BASEDIR%%/temp +%(python_path)s setup.py build develop --install-dir %%BASEDIR%%\\temp +copy "%%BASEDIR%%\\temp\\*.egg-link" "%%CONFIG_DIR%%\\plugins" +rd /s /q %%BASEDIR%%\\temp +""" + +create_plugin() diff --git a/deluge/scripts/deluge_remote.py b/deluge/scripts/deluge_remote.py new file mode 100644 index 0000000..d983e53 --- /dev/null +++ b/deluge/scripts/deluge_remote.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# +# This software is in the public domain, furnished "as is", without technical +# support, and with no warranty, express or implied, as to its usefulness for +# any purpose. +# +# deluge_config.py +# This code (at least in theory) allows one to alter configuration settings +# on a deluge backend. At the moment, though, it only alters the parameters +# that I've found useful to change. +# +# Authour: Garett Harnish + +import logging +import sys +from optparse import OptionParser + + +def is_float_digit(string): + if string.isdigit(): + return True + else: + try: + float(string) + return True + except ValueError: + return False + + +# set up command-line options +parser = OptionParser() +parser.add_option( + '--port', + help='port for deluge backend host (default: 58846)', + default='58846', + dest='port', +) +parser.add_option( + '--host', + help='hostname of deluge backend to connect to (default: localhost)', + default='localhost', + dest='host', +) +parser.add_option( + '--max_active_limit', + dest='max_active_limit', + help='sets the absolute maximum number of active torrents on the deluge backend', +) +parser.add_option( + '--max_active_downloading', + dest='max_active_downloading', + help='sets the maximum number of active downloading torrents on the deluge backend', +) +parser.add_option( + '--max_active_seeding', + dest='max_active_seeding', + help='sets the maximum number of active seeding torrents on the deluge backend', +) +parser.add_option( + '--max_download_speed', + help='sets the maximum global download speed on the deluge backend', + dest='max_download_speed', +) +parser.add_option( + '--max_upload_speed', + help='sets the maximum global upload speed on the deluge backend', + dest='max_upload_speed', +) +parser.add_option( + '--debug', + help='outputs debug information to the console', + default=False, + action='store_true', + dest='debug', +) + +# grab command-line options +(options, args) = parser.parse_args() + +if not options.debug: + logging.disable(logging.ERROR) + +settings = {} + +# set values if set and valid +if options.max_active_limit: + if options.max_active_limit.isdigit() and int(options.max_active_limit) >= 0: + settings['max_active_limit'] = int(options.max_active_limit) + else: + sys.stderr.write('ERROR: Invalid max_active_limit parameter!\n') + sys.exit(-1) + +if options.max_active_downloading: + if ( + options.max_active_downloading.isdigit() + and int(options.max_active_downloading) >= 0 + ): + settings['max_active_downloading'] = int(options.max_active_downloading) + else: + sys.stderr.write('ERROR: Invalid max_active_downloading parameter!\n') + sys.exit(-1) + +if options.max_active_seeding: + if options.max_active_seeding.isdigit() and int(options.max_active_seeding) >= 0: + settings['max_active_seeding'] = int(options.max_active_seeding) + else: + sys.stderr.write('ERROR: Invalid max_active_seeding parameter!\n') + sys.exit(-1) + +if options.max_download_speed: + if is_float_digit(options.max_download_speed) and ( + float(options.max_download_speed) >= 0.0 + or float(options.max_download_speed) == -1.0 + ): + settings['max_download_speed'] = float(options.max_download_speed) + else: + sys.stderr.write('ERROR: Invalid max_download_speed parameter!\n') + sys.exit(-1) + +if options.max_upload_speed: + if is_float_digit(options.max_upload_speed) and ( + float(options.max_upload_speed) >= 0.0 + or float(options.max_upload_speed) == -1.0 + ): + settings['max_upload_speed'] = float(options.max_upload_speed) + else: + sys.stderr.write('ERROR: Invalid max_upload_speed parameter!\n') + sys.exit(-1) + +# If there is something to do ... +if settings: + # create connection to daemon + from deluge.ui.client import sclient as client + + client.set_core_uri('http://' + options.host + ':' + options.port) + + # commit configurations changes + client.set_config(settings) |