Compare commits

..

128 Commits

Author SHA1 Message Date
c413032c96 Tag 1.1.2 release 2009-01-31 22:18:11 +00:00
0cc4efc455 version bump 2009-01-31 21:27:50 +00:00
c5e5a3d4e7 lang sync for release 2009-01-31 21:14:26 +00:00
8a5aa3a150 release prep 2009-01-31 20:58:29 +00:00
b0f9117a3d lt sync 3225 2009-01-28 16:19:36 +00:00
040b4938e1 Fix license headers
Remove ui/webui/ssl from package_data since it doesn't exist
2009-01-28 00:36:33 +00:00
bc34d864ff Remove pythonize module since it's no longer used 2009-01-27 23:44:26 +00:00
4a07a33503 Update copyright years in aboutdialog 2009-01-27 23:34:03 +00:00
933228a82a Fix saving files/peers tab state when no column is sorted 2009-01-27 21:45:44 +00:00
a777233a7a Fix typo 2009-01-27 21:41:48 +00:00
79fb4b260d Fix #761 use proper theme colours in sidebar 2009-01-27 21:21:15 +00:00
3f414f4bdf lt sync 3219 2009-01-27 19:24:07 +00:00
04bebad82f lt sync 3218 2009-01-27 17:27:23 +00:00
0808bdaa0f Fix up some copyright headers 2009-01-27 00:21:53 +00:00
fc5d436021 Remove 'state_upgrade.py' script since this functionality is included in Deluge now. 2009-01-27 00:17:43 +00:00
eca8ced2c8 lang sync 2009-01-25 03:21:54 +00:00
6847db1304 ltsync 3211 2009-01-25 02:51:01 +00:00
e8d75cffe3 lt sync 3209 2009-01-25 02:10:39 +00:00
2de48097c0 Prep for release 2009-01-25 00:33:49 +00:00
7bc8c9fa36 Update python bindings from libtorrent svn 2009-01-24 05:31:45 +00:00
d268ed4955 Fix setting outgoing ports 2009-01-24 05:25:37 +00:00
7376a1e125 lt sync 3205 2009-01-24 04:12:32 +00:00
df92d3c468 Fix when sorting # column, downloads should be on top 2009-01-23 05:25:31 +00:00
190854dc2f Fix setting file priorities when adding a torrent 2009-01-22 08:22:50 +00:00
6e778aadf5 Fix being able to select/deselect files in the add torrent dialog 2009-01-22 06:01:34 +00:00
8249ad86ea Fix setting Peer TOS byte 2009-01-21 14:03:26 +00:00
631768f01b Fix torrents not displaying properly after disconnecting and
reconnecting to the daemon
2009-01-20 11:45:43 +00:00
b7f9fec075 Default to ERROR logging level if the supplied logging level is invalid 2009-01-18 06:24:49 +00:00
5672db086a lt sync 3187 2009-01-17 22:03:36 +00:00
adbe77647a Fix translating speed units in status tab when a per-torrent limit is
set
2009-01-17 22:02:38 +00:00
d44a479177 Fix crashing in AddTorrentDialog when removing torrents from the list
Do not allow duplicate torrents in the AddTorrentDialog
2009-01-17 21:58:02 +00:00
09f9c042e4 Fix being able to connect to a local daemon from another user account 2009-01-16 20:11:05 +00:00
cfa479ba3e add tooltip to popup about windows 2009-01-16 04:47:44 +00:00
4fe3780528 add sensitivity to changelog 2009-01-16 03:57:58 +00:00
f96fb69fda make popup insensitive on windows 2009-01-16 03:41:13 +00:00
675aed2094 Add support for more tracker icons 2009-01-16 03:31:21 +00:00
e31eab5303 Fix #729 tracker icons not being saved in the correct location 2009-01-16 03:18:43 +00:00
1bf7422904 Fix saving Files tab and Peers tab state 2009-01-16 03:13:37 +00:00
740506343f Fix remembering sorted column in the torrent list 2009-01-16 03:01:41 +00:00
91dfbffd69 Fix opening links from Help menu and others 2009-01-16 02:45:36 +00:00
bc0df7b6a7 Fix the -l, --logfile option 2009-01-16 00:35:07 +00:00
8bd576f636 Fix [4491] which broke the oldstateupgrade 2009-01-14 02:45:11 +00:00
7a645486ab Fix bdecoding some torrent files 2009-01-14 01:15:00 +00:00
14006f83b5 windows updates to switch to boost 1.37 and python 2.6 2009-01-12 23:30:33 +00:00
f83a180de8 Update bencode.py from BitTorrent-5.2.2. This changes the license to
Python Software Foundation License Version 2.3.
2009-01-12 22:44:27 +00:00
535ad73c04 Append a new line to the localclient entry 2009-01-12 19:55:57 +00:00
4390e14485 add changelog line on auto-complete fix (ConsuleUI) 2009-01-11 23:56:33 +00:00
eb568a8cba Add codename to release 2009-01-11 22:59:09 +00:00
2b80b801b1 merged r4524 from trunk to branches/1.1.0_RC 2009-01-11 00:14:30 +00:00
107079fbab lt sync 3143 2009-01-10 21:25:43 +00:00
57c1950d40 lang sync 2009-01-10 18:50:44 +00:00
40e700cf86 prep release 2009-01-10 18:20:15 +00:00
f1f50dc447 update credits for 1.x 2009-01-10 07:37:53 +00:00
2f3edc0352 Merge copyright info from trunk 2009-01-10 00:05:31 +00:00
959f6e550f fix debian bug #510948 missing dep 2009-01-09 20:00:17 +00:00
748a82a23b Set 'max_half_open_connections' default to 50 on non-Windows systems 2009-01-09 04:21:26 +00:00
009b34bccb Tell the user which UI was tried when unable to start 2009-01-08 22:25:46 +00:00
6cd794fa18 Fix missing status key in notification get_status 2009-01-08 21:31:04 +00:00
b258a9c340 Added '-s', '--set-default-ui' option to deluge 2009-01-08 20:27:24 +00:00
d8447aea72 Update credits and author list 2009-01-08 03:35:22 +00:00
d5c12a47c2 Fix installing 2009-01-08 02:31:01 +00:00
0d7cf1af81 add mime icons to the white theme
couple of stubs in the add window
2009-01-07 21:12:05 +00:00
64d94eb95f update changelog 2009-01-07 21:05:33 +00:00
28eda6caa0 improve the files table in the add torrent window
add some mimetype support in

improve the merge_changes.py script
2009-01-07 20:42:57 +00:00
150c803d19 Allow torrents to be removed from the add torrent window 2009-01-07 19:06:28 +00:00
d88fe0e894 more improvements to the options tab 2009-01-07 18:13:05 +00:00
225afdec1f template update 2009-01-07 03:55:21 +00:00
4175289690 fix gtk-cancel translation setting in remove dialog 2009-01-07 03:41:52 +00:00
d669e6e864 add stub for filepicker to trunk
start implementing the options dialog
add another not implemented alert to edit trackers
update changelog
2009-01-07 01:28:19 +00:00
1a70007697 fixes ticket #684 2009-01-07 01:00:56 +00:00
0208e59ad6 fix bug when used from ipod 2009-01-07 00:31:08 +00:00
1bf2fb47b7 add a couple of alerts to inform users of parts that aren't implemented yet 2009-01-07 00:04:42 +00:00
8deee64007 Fix issue in get_tracker_host when the torrent has no tracker 2009-01-06 12:50:49 +00:00
42cceabd8e fix crash on trying to convert *very* old 0.5 config files 2009-01-06 02:42:08 +00:00
6eb413dd1e fix notification bug on startup for already finished torrents 2009-01-06 02:27:27 +00:00
81b895bd1d Fix the display of the tracker host when it's an IP address and not a
hostname
2009-01-05 10:14:12 +00:00
2edf1fc692 Update ChangeLog 2009-01-05 05:49:19 +00:00
8c489e86d2 version up 2009-01-05 05:46:07 +00:00
530fcf255b lang sync for release 2009-01-05 05:45:17 +00:00
af1b3a6d3a Update ChangeLog 2009-01-05 05:14:07 +00:00
04d344a133 Show the Trackers sidebar filter by default 2009-01-04 08:53:06 +00:00
7226cbb53d lt sync 3129 2009-01-04 08:48:46 +00:00
f168f7e18e Fix seeding torrents from moving around when sorting the '#' column 2009-01-04 08:47:26 +00:00
923cfaab5c Fix typo 2009-01-02 21:27:43 +00:00
b3aa588650 Fix applying proxy settings 2009-01-02 21:25:26 +00:00
08b92148ca Label: Fix move on completed 2009-01-02 03:25:39 +00:00
914ae20e74 Fix folder renaming to display the change properly 2009-01-02 00:56:44 +00:00
c562024407 Hide the initial test port image 2008-12-30 23:32:39 +00:00
6aa405187f Fix exception in test open port 2008-12-30 23:28:30 +00:00
ad0b335648 Add PeerGuardian Text (Gzip) reader 2008-12-30 03:52:18 +00:00
83690d5aaf Apply blocklist prefs when the buttons are pressed. 2008-12-30 03:51:13 +00:00
df30d4b5c9 Do not continue to show 'Importing 0' progress bar if blocklist fails to
import.
2008-12-30 02:57:21 +00:00
0296f6c6e9 Fix gtkwarning regarding column width 2008-12-29 17:10:11 +00:00
7bdabc207d lt sync 3106 2008-12-29 16:58:56 +00:00
5e0d3bedef Update ChangeLog 2008-12-29 16:51:29 +00:00
e23a4c5da7 update changelog for rc2 2008-12-29 06:56:39 +00:00
d25b41f1de version prep for release 2008-12-29 06:50:51 +00:00
07126decab going back to vs71 2008-12-29 03:43:50 +00:00
5ee25515c1 De-santafy icons 2008-12-28 23:36:59 +00:00
f86b5dd0ad Change default blocklist url to nipfilter instead of pipfilter 2008-12-28 23:15:01 +00:00
d8a187a8f6 Fix issue that prevented torrents from being added 2008-12-28 15:38:28 +00:00
1f52a3fc20 Fix exception in metafile 2008-12-28 04:29:54 +00:00
c58e2481b9 Do not use the stored config_location value because it should only be used if the --config option is
set.
2008-12-27 21:24:50 +00:00
7b99298203 Fix exception in label plugin when saving preferences 2008-12-27 21:06:53 +00:00
2ff6b85771 Make new release checking much more robust 2008-12-27 00:13:59 +00:00
5c4b64d656 lt sync 3090 2008-12-26 22:47:24 +00:00
36539cdd1d Fix never-ending import in Blocklist on Windows 2008-12-26 22:45:43 +00:00
afb12d7403 Fix tooltip for 'Show session speed in titlebar' option 2008-12-26 08:21:13 +00:00
f31ac4bdc8 Remove Stats plugin since it wasn't intended to be in this release 2008-12-26 08:06:39 +00:00
69a0dbca4d Fix new version check 2008-12-26 08:04:31 +00:00
eced7ab068 change from vs71 to vs90 2008-12-26 04:55:20 +00:00
d73a1abbe4 vs2008sp1 fix - hydri 2008-12-26 04:52:44 +00:00
ac5b5fdefb lt sync 3081 2008-12-24 06:01:18 +00:00
1f1a0168e7 Prep for release 2008-12-24 00:56:02 +00:00
86f9184320 Fix #661 show proper tracker host when ending in .co.uk, .com.au, etc.. 2008-12-22 10:07:39 +00:00
9b1ea3d8de Santify the icons 2008-12-22 06:33:41 +00:00
e7aad6a345 update translator credits 2008-12-22 02:44:57 +00:00
aa8d18c081 Yet another typo fix 2008-12-20 23:19:24 +00:00
6bc4caa4e6 Fix more issues with TorrentOptions 2008-12-20 23:14:06 +00:00
68df5a389d Fix some typos 2008-12-20 23:06:53 +00:00
21c57e935e Clean up has_key method in TorrentOptions 2008-12-20 06:28:44 +00:00
9cd3e7cf56 Fix up to __getitem__ in TorrentOptions 2008-12-20 05:49:03 +00:00
489d805cb4 Fix some typos 2008-12-20 05:20:21 +00:00
b974f91da8 Ignore _* when doing new version check 2008-12-18 07:06:06 +00:00
4b1eecf026 Do not fork in osx 2008-12-18 06:14:49 +00:00
01bda41ba8 lang sync 2008-12-18 04:05:21 +00:00
0e60a4903e update pots 2008-12-18 03:46:55 +00:00
926d71c8a1 Branch 1.1.0, trunk is now 1.2.0-dev 2008-12-18 02:52:24 +00:00
2831 changed files with 381450 additions and 651401 deletions

6
.gitattributes vendored
View File

@ -1,6 +0,0 @@
.gitattributes export-ignore
.gitmodules export-ignore
.gitignore export-ignore
*.py diff=python
ext-all.js diff=minjs
*.state -merge -text

View File

@ -1,100 +0,0 @@
name: Package
on:
push:
tags:
- "deluge-*"
- "!deluge*-dev*"
branches:
- develop
pull_request:
types: [labeled, opened, synchronize, reopened]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
inputs:
ref:
description: "Enter a tag or commit to package"
default: ""
jobs:
windows_package:
runs-on: windows-2019
if: (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'package'))
strategy:
matrix:
arch: [x64, x86]
python: ["3.9"]
libtorrent: [2.0.6, 1.2.15]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
with:
fetch-depth: 0
# Checkout Deluge source to subdir to enable packaging any tag/commit
- name: Checkout Deluge source
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.ref }}
fetch-depth: 0
path: deluge_src
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python}}
architecture: ${{ matrix.arch }}
cache: pip
- name: Prepare pip
run: python -m pip install wheel
- name: Install GTK
run: |
$WebClient = New-Object System.Net.WebClient
$WebClient.DownloadFile("https://github.com/deluge-torrent/gvsbuild-release/releases/download/latest/gvsbuild-py${{ matrix.python }}-vs16-${{ matrix.arch }}.zip","C:\GTK.zip")
7z x C:\GTK.zip -oc:\GTK
echo "C:\GTK\release\lib" | Out-File -FilePath $env:GITHUB_PATH -Append
echo "C:\GTK\release\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
echo "C:\GTK\release" | Out-File -FilePath $env:GITHUB_PATH -Append
python -m pip install --no-index --find-links="C:\GTK\release\python" pycairo PyGObject
- name: Install Python dependencies
run: >
python -m pip install
twisted[tls]==22.4.0
libtorrent==${{ matrix.libtorrent }}
pyinstaller==4.10
pygame
-r requirements.txt
- name: Install Deluge
working-directory: deluge_src
run: |
python -m pip install .
python setup.py install_scripts
- name: Freeze Deluge
working-directory: packaging/win
run: |
pyinstaller --clean delugewin.spec --distpath freeze
- name: Fix OpenSSL for libtorrent x64
if: ${{ matrix.arch == 'x64' }}
working-directory: packaging/win/freeze/Deluge
run: |
cp libssl-1_1.dll libssl-1_1-x64.dll
cp libcrypto-1_1.dll libcrypto-1_1-x64.dll
- name: Make Deluge Installer
working-directory: ./packaging/win
run: |
python setup_nsis.py
makensis /Darch=${{ matrix.arch }} deluge-win-installer.nsi
- uses: actions/upload-artifact@v2
with:
name: deluge-py${{ matrix.python }}-lt${{ matrix.libtorrent }}-${{ matrix.arch }}
path: packaging/win/*.exe

View File

@ -1,98 +0,0 @@
name: CI
on:
push:
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
env:
SETUPTOOLS_ENABLE_FEATURES: "legacy-editable"
jobs:
test-linux:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.7", "3.10"]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "requirements*.txt"
- name: Sets env var for security
if: (github.event_name == 'pull_request' && contains(github.event.pull_request.body, 'security_test')) || (github.event_name == 'push' && contains(github.event.head_commit.message, 'security_test'))
run: echo "SECURITY_TESTS=True" >> $GITHUB_ENV
- name: Install dependencies
run: |
pip install --upgrade pip wheel
pip install -r requirements.txt -r requirements-tests.txt
pip install -e .
- name: Install security dependencies
if: contains(env.SECURITY_TESTS, 'True')
run: |
wget -O- $TESTSSL_URL$TESTSSL_VER | tar xz
mv -t deluge/tests/data testssl.sh-$TESTSSL_VER/testssl.sh testssl.sh-$TESTSSL_VER/etc/;
env:
TESTSSL_VER: 3.0.6
TESTSSL_URL: https://codeload.github.com/drwetter/testssl.sh/tar.gz/refs/tags/v
- name: Setup core dump directory
run: |
sudo mkdir /cores/ && sudo chmod 777 /cores/
echo "/cores/%E.%p" | sudo tee /proc/sys/kernel/core_pattern
- name: Test with pytest
run: |
ulimit -c unlimited # Enable core dumps to be captured
python -c 'from deluge._libtorrent import lt; print(lt.__version__)';
catchsegv python -X dev -m pytest -v -m "not (todo or gtkui)" deluge
- uses: actions/upload-artifact@v2
# capture all crashes as build artifacts
if: failure()
with:
name: crashes
path: /cores
test-windows:
runs-on: windows-2019
strategy:
matrix:
python-version: ["3.7", "3.10"]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "requirements*.txt"
- name: Install dependencies
run: |
pip install --upgrade pip wheel
pip install -r requirements.txt -r requirements-tests.txt
pip install -e .
- name: Test with pytest
run: |
python -c 'import libtorrent as lt; print(lt.__version__)';
pytest -v -m "not (todo or gtkui or security)" deluge

View File

@ -1,45 +0,0 @@
name: Docs
# Controls when the action will run.
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: "3.8"
- name: Cache pip
uses: actions/cache@v2
with:
# This path is specific to Ubuntu
path: ~/.cache/pip
# Look to see if there is a cache hit for the corresponding requirements file
key: ${{ runner.os }}-pip-${{ hashFiles('requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Install dependencies
run: |
pip install --upgrade pip wheel
pip install tox
sudo apt-get install enchant-2
- name: Test with tox
env:
TOX_ENV: docs
run: |
tox -e $TOX_ENV

View File

@ -1,17 +0,0 @@
name: Linting
on:
push:
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- name: Run pre-commit linting
uses: pre-commit/action@v2.0.2

25
.gitignore vendored
View File

@ -1,25 +0,0 @@
*~
build
.cache
dist
docs/source/modules/deluge*.rst
*.egg-info/
*.dist-info/
*.egg
*.log
__pycache__/
*.py[cod]
*.tar.*
.tox/
deluge/i18n/*/
deluge.pot
deluge/ui/web/js/*.js
deluge/ui/web/js/extjs/ext-extensions*.js
*.desktop
*.appdata.xml
.build_data*
osx/app
RELEASE-VERSION
.venv*
# used by setuptools to cache downloaded eggs
/.eggs

View File

@ -1,51 +0,0 @@
default_language_version:
python: python3
exclude: >
(?x)^(
deluge/ui/web/docs/template/.*|
deluge/tests/data/.*svg|
)$
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
name: Fmt Black
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.5.1
hooks:
- id: prettier
name: Fmt Prettier
# Workaround to list modified files only.
args: [--list-different]
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
name: Fmt isort
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
name: Chk Flake8
additional_dependencies:
- pep8-naming==0.12.1
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: double-quote-string-fixer
name: Fix Double-quotes
- id: end-of-file-fixer
name: Fix End-of-files
exclude_types: [javascript, css]
- id: mixed-line-ending
name: Fix Line endings
args: [--fix=auto]
- id: trailing-whitespace
name: Fix Trailing whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
hooks:
- id: pyupgrade
args: [--py36-plus]
stages: [manual]

View File

@ -1,6 +0,0 @@
deluge/ui/web/css/ext-*.css
deluge/ui/web/js/extjs/ext-*.js
deluge/ui/web/docs/
deluge/ui/web/themes/images/
*.py*
*.html

View File

@ -1,13 +0,0 @@
trailingComma: "es5"
tabWidth: 4
singleQuote: true
overrides:
- files:
- "*.yaml"
- ".*.yaml"
- "*.yml"
- ".*.yml"
- "*.md"
options:
tabWidth: 2
singleQuote: false

420
.pylintrc
View File

@ -1,420 +0,0 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Use multiple processes to speed up Pylint.
jobs=2
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=
# Allow optimization of some AST trees. This will activate a peephole AST
# optimizer, which will apply various small optimizations. For instance, it can
# be used to obtain the result of joining multiple strings with the addition
# operator. Joining a lot of strings can lead to a maximum recursion error in
# Pylint and this flag can prevent that. It has one side effect, the resulting
# AST will be different than the one from reality. This option is deprecated
# and it will be removed in Pylint 2.0.
optimize-ast=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
#
# Arranged by category and use symbolic names instead of ids.
disable=
# Convention
missing-docstring, invalid-name, bad-continuation,
# Error
no-member, no-name-in-module,
# Information
locally-disabled,
# Refactor
no-self-use, too-many-arguments, too-many-branches, too-many-instance-attributes,
too-many-locals, too-few-public-methods, too-many-public-methods, too-many-statements,
# Refactor msgs that should eventually be enabled:
redefined-variable-type, too-many-ancestors,
too-many-nested-blocks, too-many-return-statements,
# Warning
unused-argument, protected-access, import-error, unused-variable,
attribute-defined-outside-init,
# Warning msgs that should eventually be enabled:
arguments-differ, global-statement, fixme, broad-except
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=parseable
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]". This option is deprecated
# and it will be removed in Pylint 2.0.
files-output=no
# Tells whether to display a full report or only the messages
reports=no
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[BASIC]
# Good variable names which should always be accepted, separated by a comma
good-names=d,i,j,k,ex,Run,_,log
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
property-classes=abc.abstractproperty
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,40}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[ELIF]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=120
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1550
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=LF
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=_,_n,__request__,WindowsError
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=SQLObject,twisted.internet.reactor
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
[DESIGN]
# Maximum number of arguments for function / method
max-args=7
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of boolean expressions in a if statement
max-bool-expr=5
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception

View File

@ -1,22 +0,0 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Optionally build your docs in additional formats such as PDF and ePub
formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7
install:
- requirements: requirements.txt
- requirements: docs/requirements.txt
- method: setuptools
path: .

790
AUTHORS
View File

@ -1,790 +0,0 @@
Authors:
* Andrew Resch ('andar') <andrewresch@gmail.com>
* Damien Churchill ('damoxc') <damoxc@gmail.com>
Main Developers:
* Andrew Resch
* Damien Churchill
* John Garland ('johnnyg') <johnnybg+deluge@gmail.com>
* Calum Lind ('cas') <calumlind+deluge@gmail.com>
libtorrent (http://www.libtorrent.org):
* Arvid Norberg
Contributors (and Past Developers):
* Zach Tibbitts <zach@collegegeek.org>
* Alon Zakai ('Kripken') <kripkensteiner@gmail.com>
* Marcos Mobley ('markybob') <markybob@gmail.com>
* Alex Dedul
* Sadrul Habib Chowdhury
* Ido Abramovich <ido.deluge@gmail.com>
* Martijn Voncken <mvoncken@gmail.com>
* Mark Stahler ('kramed') <markstahler@gmail.com>
* Pedro Algarvio ('s0undt3ch') <ufs@ufsoft.org>
* Cristian Greco ('cgreco') <cristian@regolo.cc>
* Chase Sterling ('gazpachoKing') <chase.sterling@gmail.com>
Plugin Developers:
* Autoadd : Chase Sterling
* Blocklist : John Garland
* Execute : Damien Churchill
* Extractor : Andrew Resch
* Label : Martijn Voncken
* Notifications : Pedro Algarvio
* Scheduler : Andrew Resch
* Webui : Damien Churchill
Images Authors:
* files: deluge/ui/data/pixmaps/*.svg, *.png
deluge/ui/web/icons/active.png, alert.png, all.png, checking.png, dht.png,
downloading.png, inactive.png, queued.png, seeding.png, traffic.png
deluge/ui/web/images/deluge*.png
deluge/ui/web/icons/apple-pre-*.png, deluge*.png
copyright: Calum Lind
license: GPLv3
* files: deluge/plugins/blocklist/blocklist/data/*.png
deluge/ui/data/pixmaps/tracker_warning16.png, tracker_all16.png, lock48.png
copyright: Gnome Icon Theme
license: GPLv2
url: http://ftp.acc.umu.se/pub/GNOME/sources/gnome-icon-theme
* files: deluge/ui/data/pixmaps/magnet*.svg, *.png
copyright: Matias Wilkman
license:
* files: deluge/ui/data/pixmaps/flags/*.png
copyright: Mark James <mjames@gmail.com>
license: Public Domain
url: http://famfamfam.com/lab/icons/flags/
* files: deluge/ui/web/icons/*.png
exceptions: apple-pre-*.png, active.png, alert.png, all.png, deluge.png, dht.png,
downloading.png, inactive.png, queued.png, seeding.png, traffic.png
copyright: Yusuke Kamiyamane <p@yusukekamiyamane.com>
license: Creative Commons Attribution 3.0 License
url: http://p.yusukekamiyamane.com/
* files: deluge/ui/web/images/spinner.gif, spinner-split.gif
copyright: Steven Chim
license: BSD license
url: http://members.upc.nl/j.chim/ext/spinner2/ext-spinner.html
Translation Contributors:
* files: deluge/i18n/*.po
Aaron Wang Shi
abbigss
ABCdatos
Abcx
Actam
Adam
adaminikisi
adi_oporanu
Adrian Goll
afby
Ahmades
Ahmad Farghal
Ahmad Gharbeia أحمد غربية
akira
Aki Sivula
Alan Pepelko
Alberto
Alberto Ferrer
alcatr4z
AlckO
Aleksej Korgenkov
Alessio Treglia
Alexander Ilyashov
Alexander Matveev
Alexander Saltykov
Alexander Taubenkorb
Alexander Telenga
Alexander Yurtsev
Alexandre Martani
Alexandre Rosenfeld
Alexandre Sapata Carbonell
Alexey Osipov
Alin Claudiu Radut
allah
AlSim
Alvaro Carrillanca P.
A.Matveev
Andras Hipsag
András Kárász
Andrea Ratto
Andreas Johansson
Andreas Str
André F. Oliveira
AndreiF
andrewh
Angel Guzman Maeso
Aníbal Deboni Neto
animarval
Antonio Cono
antoniojreyes
Anton Shestakov
Anton Yakutovich
antou
Arkadiusz Kalinowski
Artin
artir
Astur
Athanasios Lefteris
Athmane MOKRAOUI (ButterflyOfFire)
Augusta Carla Klug
Avoledo Marco
axaard
AxelRafn
Axezium
Ayont
b3rx
Bae Taegil
Bajusz Tamás
Balaam's Miracle
Ballestein
Bent Ole Fosse
berto89
bigx
Bjorn Inge Berg
blackbird
Blackeyed
blackmx
BlueSky
Blutheo
bmhm
bob00work
boenki
Bogdan Bădic-Spătariu
bonpu
Boone
boss01
Branislav Jovanović
bronze
brownie
Brus46
bumper
butely
BXCracer
c0nfidencal
Can Kaya
Carlos Alexandro Becker
cassianoleal
Cédric.h
César Rubén
chaoswizard
Chen Tao
chicha
Chien Cheng Wei
Christian Kopac
Christian Widell
Christoffer Brodd-Reijer
christooss
CityAceE
Clopy
Clusty
cnu
Commandant
Constantinos Koniaris
Coolmax
cosmix
Costin Chirvasuta
CoVaLiDiTy
cow_2001
Crispin Kirchner
crom
Cruster
Cybolic
Dan Bishop
Danek
Dani
Daniel Demarco
Daniel Ferreira
Daniel Frank
Daniel Holm
Daniel Høyer Iversen
Daniel Marynicz
Daniel Nylander
Daniel Patriche
Daniel Schildt
Daniil Sorokin
Dante Díaz
Daria Michalska
DarkenCZ
Darren
Daspah
David Eurenius
davidhjelm
David Machakhelidze
Dawid Dziurdzia
Daya Adianto
dcruz
Deady
Dereck Wonnacott
Devgru
Devid Antonio FiloniDevilDogTG
di0rz`
Dialecti Valsamou
Diego Medeiros
Dkzoffy
Dmitrij D. Czarkoff
Dmitriy Geels
Dmitry Olyenyov
Dominik Kozaczko
Dominik Lübben
doomster
Dorota Król
Doyen Philippe
Dread Knight
DreamSonic
duan
Duong Thanh An
DvoglavaZver
dwori
dylansmrjones
Ebuntor
Edgar Alejandro Jarquin Flores
Eetu
ekerazha
Elias Julkunen
elparia
Emberke
Emiliano Goday Caneda
EndelWar
eng.essam
enubuntu
ercangun
Erdal Ronahi
ergin üresin
Eric
Éric Lassauge
Erlend Finvåg
Errdil
ethan shalev
Evgeni Spasov
ezekielnin
Fabian Ordelmans
Fabio Mazanatti
Fábio Nogueira
FaCuZ
Felipe Lerena
Fernando Pereira
fjetland
Florian Schäfer
FoBoS
Folke
Force
fosk
fragarray
freddeg
Frédéric Perrin
Fredrik Kilegran
FreeAtMind
Fulvio Ciucci
Gabor Kelemen
Galatsanos Panagiotis
Gaussian
gdevitis
Georg Brzyk
George Dumitrescu
Georgi Arabadjiev
Georg Sieber
Gerd Radecke
Germán Heusdens
Gianni Vialetto
Gigih Aji Ibrahim
Giorgio Wicklein
Giovanni Rapagnani
Giuseppe
gl
glen
granjerox
Green Fish
greentea
Greyhound
G. U.
Guillaume BENOIT
Guillaume Pelletier
Gustavo Henrique Klug
gutocarvalho
Guybrush88
Hans Rødtang
HardDisk
Hargas Gábor
Heitor Thury Barreiros Barbosa
helios91940
helix84
Helton Rodrigues
Hendrik Luup
Henrique Ferreiro
Henry Goury-Laffont
Hezy Amiel
hidro
hoball
hokten
Holmsss
hristo.num
Hubert Życiński
Hyo
Iarwain
ibe
ibear
Id2ndR
Igor Zubarev
IKON (Ion)
imen
Ionuț Jula
Isabelle STEVANT
István Nyitrai
Ivan Petrovic
Ivan Prignano
IvaSerge
jackmc
Jacks0nxD
Jack Shen
Jacky Yeung
Jacques Stadler
Janek Thomaschewski
Jan Kaláb
Jan Niklas Hasse
Jasper Groenewegen
Javi Rodríguez
Jayasimha (ಜಯಸಿಂಹ)
jeannich
Jeff Bailes
Jesse Zilstorff
Joan Duran
João Santos
Joar Bagge
Joe Anderson
Joel Calado
Johan Linde
John Garland
Jojan
jollyr0ger
Jonas Bo Grimsgaard
Jonas Granqvist
Jonas Slivka
Jonathan Zeppettini
Jørgen
Jørgen Tellnes
josé
José Geraldo Gouvêa
José Iván León Islas
José Lou C.
Jose Sun
Jr.
Jukka Kauppinen
Julián Alarcón
julietgolf
Jusic
Justzupi
Kaarel
Kai Thomsen
Kalman Tarnay
Kamil Páral
Kane_F
kaotiks@gmail.com
Kateikyoushii
kaxhinaz
Kazuhiro NISHIYAMA
Kerberos
Keresztes Ákos
kevintyk
kiersie
Kimbo^
Kim Lübbe
kitzOgen
Kjetil Rydland
kluon
kmikz
Knedlyk
koleoptero
Kőrösi Krisztián
Kouta
Krakatos
Krešo Kunjas
kripken
Kristaps
Kristian Øllegaard
Kristoffer Egil Bonarjee
Krzysztof Janowski
Krzysztof Zawada
Larry Wei Liu
laughterwym
Laur Mõtus
lazka
leandrud
lê bình
Le Coz Florent
Leo
liorda
LKRaider
LoLo_SaG
Long Tran
Lorenz
Low Kian Seong
Luca Andrea Rossi
Luca Ferretti
Lucky LIX
Luis Gomes
Luis Reis
Łukasz Wyszyński
luojie-dune
maaark
Maciej Chojnacki
Maciej Meller
Mads Peter Rommedahl
Major Kong
Malaki
malde
Malte Lenz
Mantas Kriaučiūnas
Mara Sorella
Marcin
Marcin Falkiewicz
marcobra
Marco da Silva
Marco de Moulin
Marco Rodrigues
Marcos
Marcos Escalier
Marcos Mobley
Marcus Ekstrom
Marek Dębowski
Mário Buči
Mario Munda
Marius Andersen
Marius Hudea
Marius Mihai
Mariusz Cielecki
Mark Krapivner
marko-markovic
Markus Brummer
Markus Sutter
Martin
Martin Dybdal
Martin Iglesias
Martin Lettner
Martin Pihl
Masoud Kalali
mat02
Matej Urbančič
Mathias-K
Mathieu Arès
Mathieu D. (MatToufoutu)
Mathijs
Matrik
Matteo Renzulli
Matteo Settenvini
Matthew Gadd
Matthias Benkard
Matthias Mailänder
Mattias Ohlsson
Mauro de Carvalho
Max Molchanov
Me
MercuryCC
Mert Bozkurt
Mert Dirik
MFX
mhietar
mibtha
Michael Budde
Michael Kaliszka
Michalis Makaronides
Michał Tokarczyk
Miguel Pires da Rosa
Mihai Capotă
Miika Metsälä
Mikael Fernblad
Mike Sierra
mikhalek
Milan Prvulović
Milo Casagrande
Mindaugas
Miroslav Matejaš
misel
mithras
Mitja Pagon
M.Kitchen
Mohamed Magdy
moonkey
MrBlonde
muczy
Münir Ekinci
Mustafa Temizel
mvoncken
Mytonn
NagyMarton
neaion
Neil Lin
Nemo
Nerijus Arlauskas
Nicklas Larsson
Nicolaj Wyke
Nicola Piovesan
Nicolas Sabatier
Nicolas Velin
Nightfall
NiKoB
Nikolai M. Riabov
Niko_Thien
niska
Nithir
noisemonkey
nomemohes
nosense
null
Nuno Estêvão
Nuno Santos
nxxs
nyo
obo
Ojan
Olav Andreas Lindekleiv
oldbeggar
Olivier FAURAX
orphe
osantana
Osman Tosun
OssiR
otypoks
ounn
Oz123
Özgür BASKIN
Pablo Carmona A.
Pablo Ledesma
Pablo Navarro Castillo
Paco Molinero
Pål-Eivind Johnsen
pano
Paolo Naldini
Paracelsus
Patryk13_03
Patryk Skorupa
PattogoTehen
Paul Lange
Pavcio
Paweł Wysocki
Pedro Brites Moita
Pedro Clemente Pereira Neto
Pekka "PEXI" Niemistö
Penegal
Penzo
perdido
Peter Kotrcka
Peter Skov
Peter Van den Bosch
Petter Eklund
Petter Viklund
phatsphere
Phenomen
Philipi
Philippides Homer
phoenix
pidi
Pierre Quillery
Pierre Rudloff
Pierre Slamich
Pietrao
Piotr Strębski
Piotr Wicijowski
Pittmann Tamás
Playmolas
Prescott
Prescott_SK
pronull
Przemysław Kulczycki
Pumy
pushpika
PY
qubicllj
r21vo
Rafał Barański
rainofchaos
Rajbir
ras0ir
Rat
rd1381
Renato
Rene Hennig
Rene Pärts
Ricardo Duarte
Richard
Robert Hrovat
Roberth Sjonøy
Robert Lundmark
Robin Jakobsson
Robin Kåveland
Rodrigo Donado
Roel Groeneveld
rohmaru
Rolf Christensen
Rolf Leggewie
Roni Kantis
Ronmi
Rostislav Raykov
royto
RuiAmaro
Rui Araújo
Rui Moura
Rune Svendsen
Rusna
Rytis
Sabirov Mikhail
salseeg
Sami Koskinen
Samir van de Sand
Samuel Arroyo Acuña
Samuel R. C. Vale
Sanel
Santi
Santi Martínez Cantelli
Sardan
Sargate Kanogan
Sarmad Jari
Saša Bodiroža
sat0shi
Saulius Pranckevičius
Savvas Radevic
Sebastian Krauß
Sebastián Porta
Sedir
Sefa Denizoğlu
sekolands
Selim Suerkan
semsomi
Sergii Golovatiuk
setarcos
Sheki
Shironeko
Shlomil
silfiriel
Simone Tolotti
Simone Vendemia
sirkubador
Sławomir Więch
slip
slyon
smoke
Sonja
spectral
spin_555
spitf1r3
Spiziuz
Spyros Theodoritsis
SqUe
Squigly
srtck
Stefan Horning
Stefano Maggiolo
Stefano Roberto Soleti
steinberger
Stéphane Travostino
Stephan Klein
Steven De Winter
Stevie
Stian24
stylius
Sukarn Maini
Sunjae Park
Susana Pereira
szymon siglowy
takercena
TAS
Taygeto
temy4
texxxxxx
thamood
Thanos Chatziathanassiou
Tharawut Paripaiboon
Theodoor
Théophane Anestis
Thor Marius K. Høgås
Tiago Silva
Tiago Sousa
Tikkel
tim__b
Tim Bordemann
Tim Fuchs
Tim Kornhammar
Timo
Timo Jyrinki
Timothy Babych
TitkosRejtozo
Tom
Tomas Gustavsson
Tomas Valentukevičius
Tomasz Dominikowski
Tomislav Plavčić
Tom Mannerhagen
Tommy Mikkelsen
Tom Verdaat
Tony Manco
Tor Erling H. Opsahl
Toudi
tqm_z
Trapanator
Tribaal
Triton
TuniX12
Tuomo Sipola
turbojugend_gr
Turtle.net
twilight
tymmej
Ulrik
Umarzuki Mochlis
unikob
Vadim Gusev
Vagi
Valentin Bora
Valmantas Palikša
VASKITTU
Vassilis Skoullis
vetal17
vicedo
viki
villads hamann
Vincent Garibal
Vincent Ortalda
vinchi007
Vinícius de Figueiredo Silva
Vinzenz Vietzke
virtoo
virtual_spirit
Vitor Caike
Vitor Lamas Gatti
Vladimir Lazic
Vladimir Sharshov
Wanderlust
Wander Nauta
Ward De Ridder
WebCrusader
webdr
Wentao Tang
wilana
Wilfredo Ernesto Guerrero Campos
Wim Champagne
World Sucks
Xabi Ezpeleta
Xavi de Moner
XavierToo
XChesser
Xiaodong Xu
xyb
Yaron
Yasen Pramatarov
YesPoX
Yuren Ju
Yves MATHIEU
zekopeko
zhuqin
Zissan
Γιάννης Κατσαμπίρης
Артём Попов
Миша
Шаймарданов Максим
蔡查理

View File

@ -1,259 +0,0 @@
# Changelog
## 2.1.1 (2022-07-10)
### Core
- Fix missing trackers added via magnet
- Fix handling magnets with tracker tiers
## 2.1.0 (2022-06-28)
### Breaking changes
- Python 2 support removed (Python >= 3.6)
- libtorrent minimum requirement increased (>= 1.2).
### Core
- Add support for SVG tracker icons.
- Fix tracker icon error handling.
- Fix cleaning-up tracker icon temp files.
- Fix Plugin manager to handle new metadata 2.1.
- Hide passwords in config logs.
- Fix cleaning-up temp files in add_torrent_url.
- Fix KeyError in sessionproxy after torrent delete.
- Remove libtorrent deprecated functions.
- Fix file_completed_alert handling.
- Add plugin keys to get_torrents_status.
- Add support for pygeoip dependency.
- Fix crash logging to Windows protected folder.
- Add is_interface and is_interface_name to validate network interfaces.
- Fix is_url and is_infohash error with None value.
- Fix load_libintl error.
- Add support for IPv6 in host lists.
- Add systemd user services.
- Fix refresh and expire the torrent status cache.
- Fix crash when logging errors initializing gettext.
### Web UI
- Fix ETA column sorting in correct order (#3413).
- Fix defining foreground and background colors.
- Accept charset in content-type for json messages.
- Fix 'Complete Seen' and 'Completed' sorting.
- Fix encoding HTML entities for torrent attributes to prevent XSS.
### Gtk UI
- Fix download location textbox width.
- Fix obscured port number in Connection Manager.
- Increase connection manager default height.
- Fix bug with setting move completed in Options tab.
- Fix adding daemon accounts.
- Add workaround for crash on Windows with ico or gif icons.
- Hide account password length in log.
- Added a torrent menu option for magnet copy.
- Fix unable to prefetch magnet in thinclient mode.
- Use GtkSpinner when testing open port.
- Update About Dialog year.
- Fix Edit Torrents dialogs close issues.
- Fix ETA being copied to neighboring empty cells.
- Disable GTK CSD by default on Windows.
### Console UI
- Fix curses.init_pair raise ValueError on Py3.10.
- Swap j and k key's behavior to fit vim mode.
- Fix torrent details status error.
- Fix incorrect test for when a host is online.
- Add the torrent label to info command.
### AutoAdd
- Fix handling torrent decode errors.
- Fix error dialog not being shown on error.
### Blocklist
- Add frequency unit to interval label.
### Notifications
- Fix UnicodeEncodeError upon non-ascii torrent name.
## 2.0.5 (2021-12-15)
### WebUI
- Fix js minifying error resulting in WebUI blank screen.
- Silence erronous missing translations warning.
## 2.0.4 (2021-12-12)
### Packaging
- Fix python optional setup.py requirements
### Gtk UI
- Add detection of torrent URL on GTK UI focus
- Fix piecesbar crashing when enabled
- Remove num_blocks_cache_hits in stats
- Fix unhandled error with empty clipboard
- Add torrentdetails tabs position menu (#3441)
- Hide pygame community banner in console
- Fix cmp function for None types (#3309)
- Fix loading config with double-quotes in string
- Fix Status tab download speed and uploaded
### Web UI
- Handle torrent add failures
- Add menu option to copy magnet URI
- Fix md5sums in torrent files breaking file listing (#3388)
- Add country flag alt/title for accessibility
### Console UI
- Fix allowing use of windows-curses on Windows
- Fix hostlist status lookup errors
- Fix AttributeError setting config values
- Fix setting 'Skip' priority
### Core
- Add workaround libtorrent 2.0 file_progress error
- Fix allow enabling any plugin Python version
- Export torrent get_magnet_uri method
- Fix loading magnet with resume_data and no metadata (#3478)
- Fix httpdownloader reencoding torrent file downloads (#3440)
- Fix lt listen_interfaces not comma-separated (#3337)
- Fix unable to remove magnet with delete_copies enabled (#3325)
- Fix Python 3.8 compatibility
- Fix loading config with double-quotes in string
- Fix pickle loading non-ascii state error (#3298)
- Fix creation of pidfile via command option
- Fix for peer.client UnicodeDecodeError
- Fix show_file unhandled dbus error
### Documentation
- Add How-to guides about services.
### Stats plugin
- Fix constant session status key warnings
- Fix cairo error
### Notifications plugin
- Fix email KeyError with status name
- Fix unhandled TypeErrors on Python 3
### Autoadd plugin
- Fix magnet missing applied labels
### Execute plugin
- Fix failing to run on Windows (#3439)
## 2.0.3 (2019-06-12)
### Gtk UI
- Fix errors running on Wayland (#3265).
- Fix Peers Tab tooltip and context menu errors (#3266).
### Web UI
- Fix TypeError in Peers Tab setting country flag.
- Fix reverse proxy header TypeError (#3260).
- Fix request.base 'idna' codec error (#3261).
- Fix unable to change password (#3262).
### Extractor plugin
- Fix potential error starting plugin.
### Documentation
- Fix macOS install typo.
- Fix Windows install instructions.
## 2.0.2 (2019-06-08)
### Packaging
- Add systemd deluged and deluge-web service files to package tarball (#2034)
### Core
- Fix Python 2 compatibility issue with SimpleNamespace.
## 2.0.1 (2019-06-07)
### Packaging
- Fix `setup.py` build error without git installed.
## 2.0.0 (2019-06-06)
### Codebase
- Ported to Python 3
### Core
- Improved Logging
- Removed the AutoAdd feature on the core. It's now handled with the AutoAdd
plugin, which is also shipped with Deluge, and it does a better job and
now, it even supports multiple users perfectly.
- Authentication/Permission exceptions are now sent to clients and recreated
there to allow acting upon them.
- Updated SSL/TLS Protocol parameters for better security.
- Make the distinction between adding to the session new unmanaged torrents
and torrents loaded from state. This will break backwards compatibility.
- Pass a copy of an event instead of passing the event arguments to the
event handlers. This will break backwards compatibility.
- Allow changing ownership of torrents.
- File modifications on the auth file are now detected and when they happen,
the file is reloaded. Upon finding an old auth file with an old format, an
upgrade to the new format is made, file saved, and reloaded.
- Authentication no longer requires a username/password. If one or both of
these is missing, an authentication error will be sent to the client
which should then ask the username/password to the user.
- Implemented sequential downloads.
- Provide information about a torrent's pieces states
- Add Option To Specify Outgoing Connection Interface.
- Fix potential for host_id collision when creating hostlist entries.
### Gtk UI
- Ported to GTK3 (3rd-party plugins will need updated).
- Allow changing ownership of torrents.
- Host entries in the Connection Manager UI are now editable.
- Implemented sequential downloads UI handling.
- Add optional pieces bar instead of a regular progress bar in torrent status tab.
- Make torrent opening compatible with all Unicode paths.
- Fix magnet association button on Windows.
- Add keyboard shortcuts for changing queue position:
- Up: `Ctrl+Alt+Up`
- Down: `Ctrl+Alt+Down`
- Top: `Ctrl+Alt+Shift+Up`
- Bottom: `Ctrl+Alt+Shift+Down`
### Web UI
- Server (deluge-web) now daemonizes by default, use '-d' or '--do-not-daemonize' to disable.
- Fixed the '--base' option to work for regular use, not just with reverse proxies.
### Blocklist Plugin
- Implemented whitelist support to both core and GTK UI.
- Implemented IP filter cleaning before each update. Restarting the deluge
daemon is no longer needed.
- If "check_after_days" is 0(zero), the timer is not started anymore. It
would keep updating one call after the other. If the value changed, the
timer is now stopped and restarted using the new value.

393
ChangeLog Normal file
View File

@ -0,0 +1,393 @@
Deluge 1.1.2 - (31 January 2009)
Core:
* Fix issue where torrents get stuck Checking
GtkUI:
* Fix #761 use proper theme colours in sidebar
* Fix saving files/peers tab state when no column is sorted
Deluge 1.1.1 - (24 January 2009)
Core:
* Fix oldstateupgrader for those upgrading from 0.5.x
* Fix setting Peer TOS byte
* Fix setting outgoing ports
GtkUI:
* Fix opening links from Help menu and others
* Fix remembering sorted column in the torrent list
* Fix saving Files tab and Peers tab state
* Disable popup notification in preferences on Windows
* Fix crashing in AddTorrentDialog when removing torrents from the list
* Do not allow duplicate torrents in the AddTorrentDialog
* Fix translating speed units in status tab when a per-torrent limit is set
* Fix torrents not displaying properly after disconnecting and reconnecting to the daemon
* Fix when sorting # column, downloads should be on top
Misc:
* Fix bdecoding some torrent files
* Fix the -l, --logfile option
* Fix #729 tracker icons not being saved in the correct location
* Add support for more tracker icons
* Fix being able to connect to a local daemon from another user account
Deluge 1.1.0 - "Time gas!" (10 January 2009)
Core:
* Fix issue in get_tracker_host when the torrent has no tracker
* Fix crash while trying to convert very old 0.5 config files
GtkUI:
* Fix translation setting in remove torrent dialog
* Fix notification bug on startup for already finished torrents
Ajaxui:
* Fix loading on iPods.
* Fix sorting on the name column.
* Add "Not Implemented" alerts to some functions.
* Improve the options tab on the Add Torrent dialog
ConsoleUI:
* Fix auto-complete feature for torrents.
Misc:
* Added '-s', '--set-default-ui' option to deluge
Deluge 1.1.0_RC3 (05 January 2009)
Core:
* Fix applying proxy settings
* Fix the display of the tracker host when it's an IP address and not a hostname
GtkUI:
* Fix folder renaming to display the change properly
* Fix seeding torrents from moving around when sorting the '#' column
Plugins:
* Label: Fix move on completed
* Add 'PeerGuardian Text (GZip)' reader to the Blocklist plugin
* Apply Blocklist preferences when clicking on the buttons
Deluge 1.1.0_RC2 (29 December 2008)
Core:
* Fix new version check
* Fix issue that prevented torrents from being added
GtkUI:
* Fix tooltip for 'Show session speed in titlebar' option
Plugins:
* Remove Stats plugin since it wasn't intended to be in this release
* Fix never-ending import in Blocklist on Windows
Windows:
* Fix double-click association on Windows
* Fix Pidgin icon interference
Deluge 1.1.0_RC1 (23 December 2008)
Core:
* Implement #79 ability to change outgoing port range
* Implement #296 ability to change peer TOS byte
* Add per-torrent move on completed settings
* Implement #414 use async save_resume_data method
* FilterManager with torrent filtering in get_torrents_status , for sidebar and plugins.
* Implement #368 add torrents by infohash/magnet uri (trackerless torrents)
* Remove remaining gtk functions in common
* Tracker icons.
* Add ETA for torrents with stop at seed ratio set
* Fix #47 the state and config files are no longer invalidated when there is no diskspace
* Fix #619 return "" instead of "Infinity" if seconds == 0 in ftime
* Add -P, --pidfile option to deluged
* Basic authentication for remote access to daemon, see: http://dev.deluge-torrent.org/wiki/Authentication
GtkUI:
* Add peer progress to the peers tab
* Add ability to manually add peers
* Sorting # column will place downloaders above seeds
* Remove dependency on libtorrent for add torrent dialog
* Allow adding multiple trackers at once in the edit tracker dialog
* Implement #28 Create Torrent Dialog
* Redesiged sidebar with filters for Active and Tracker (see FilterManager)
* Implement #428 the ability to rename files and directories
* Implement #229 add date added column
* Implement #596 show speeds in title
* Fix #636 not setting the daemon's config directory when using --config= with the UI in classic mode.
* Fix #624 do not allow changing file priorities when using compact allocation
* Fix #602 re-did files/peers tab state saving/loading
* Fix gtk warnings
* Add protocol traffic statusbar item
* Rework the Remove Torrent Dialog to only have 2 options, remove data and remove from session.
* Add "Install Plugin" and "Rescan Plugins" buttons to the Plugins preferences
* Make active port test use internal graphic instead of launching browser
WebUi:
* Lots of smaller tweaks.
* All details tabs have the same features as in gtk-ui 1.0.x
* Persistent sessions #486
* Plugin improvements for easy use of templates and images in eggs. #497
* Classic template takes over some style elements from white template.
* https (for users that know how to create certificates)
* Easier apache mod_proxy use.
* Redesigned sidebar
AjaxUI:
* Hosted in a webui template.
ConsoleUI:
* New ConsoleUI written by Idoa01
* Callable from command-line for scripts.
Plugins:
* Stats plugin for graphs.
* Label plugin for grouping torrents and per torrent settings.
Misc:
* Implement #478 display UI options in usage help
* Fix #547 add description to name field per HIG entry 2.1.1.1
* Fix #531 set default log level to ERROR and add 2 command-line options, "-L, --loglevel" and "-q, --quiet".
Deluge 1.0.7 (10 December 2008)
GtkUI:
* Fix #636 not setting the daemon's config directory when using --config= with the UI in classic mode.
* Fix some minor bugs in ConnectionManager
Debian:
* Fix #571 notification-daemon-xfce dependency circle
Misc:
* Fix #547 add description to name field per HIG entry 2.1.1.1
* libtorrent updates
Plugins:
* Point default blocklist url to our server and up interval to 4 days
Deluge 1.0.6 (01 December 2008)
Core:
* Fix #475 catch unicode decoding errors
* Add an option to not include IP overhead in rate limiting (this is equivalent
to how 0.5.x behaves)
* Have default blocklist url point to our server
GtkUI:
* Display the proper downloaded value in the statistics tab
Windows:
* Fix broken graphic in new release dialog
Deluge 1.0.5 (09 November 2008)
GtkUI:
* Increase the per-torrent stop share ratio max to 99999.0
* Fix #528 make sure gtkui config file is written before exiting
* Fix UDP tracker support
WebUi:
* Javascript auto refresh for both templates.
Windows:
* Fix #577 adding torrents by drag n' drop
* Fix association in Vista
* Fix WebUI launch
Debian:
* SID packages now requires Boost 1.36
Ubuntu:
* Jaunty packages are now provided
Deluge 1.0.4 (31 October 2008)
Core:
* Fix #560 force an int value for global max connections
* Fix #545 use proper values in ratio calculation
* Fix UPnP again..
GtkUI:
* Fix #565 wait for the deluged process to start to prevent defunct processes
OS X:
* Fix issues with gettext
Windows:
* Fix starting on non-English versions of Windows
Deluge 1.0.3 (18 October 2008)
Core:
* Fix upnp - it should work on more routers now too
* Fix issue where Deluge would send improper stats to the tracker after a
pause/resume.
* Fix issue where fastresume files would be rejected when using FAT32. This
would cause the torrent to be rechecked on every startup.
* Fix ip filtering
GtkUI:
* Re-add the "Max Connections Per Second" option with a default setting of 20
WebUI:
* Fix White template for Opera
Misc:
* Deluge will now use a system libtorrent library if available.
* The build system will no longer build libtorrent if a system library is
detected.
Deluge 1.0.2 (10 October 2008)
Core:
* Fix issue where torrents will not be properly added to the session
Deluge 1.0.1 (10 October 2008)
Core:
* Change the default max global upload slots to 4 instead of -1 since libtorrent
will automatically open more slots to meet the upload speed limit.
* Fix display of tracker error messages
* Fix add_torrent_url() to download the torrent file in a thread to prevent
the main thread from blocking and causing the daemon to freeze.
* Removed the 'Maximum Connections Per Second' setting and replaced it with a
default setting of 20. This should alleviate speed issues some are experiencing.
* Changed max half-open connections default limit to 8 on XP/2000 and 4 on Vista
* Prevent being able to set file priorities for compactly allocated torrents as
it is not intended to work.
* Fix freezing on start-up issues on systems that do not have a properly
configured localhost entry.
* Change max connections default setting to 200
* Fix issue with invalid bencoding from some trackers
* Plenty of libtorrent updates that should improve core stability
GtkUI:
* Improve performance of files tab by only updating when values change
Misc:
* Fix #187 set a 5 second timer to save the config file after a config value
has been changed.
* Fix #503 change the boost lib detection logic to first look for -mt and
if not available, fall back to regular boost lib (non-multithreaded)
WebUI:
* Add enable "Auto Add" checkbox
Deluge 1.0.0 - "Sharks Are Bulletproof" (21 September 2008)
Core:
* Include GeoIP database for country look-ups
* Fix upgrading from 0.5.x state where torrents would have no trackers
Deluge 0.9.09 - "1.0.0_RC9" (15 September 2008)
Core:
* Bug fixes in libtorrent including a crash when the tracker doesn't
have 'announce' in it's url.
* Fix fastresume issue causing loss of data by deleting the fastresume file
before writing a new one
* Fix #475 the use of unicode paths when adding torrents
GtkUI:
* Fix add torrent dialog closing preventing another dialog from being shown
* Fix various issues when not using English
* Fix setting file priorities on folders
Deluge 0.9.08 - "1.0.0_RC8" (27 August 2008)
Core:
* Attempt to automatically upgrade a 0.5.x state file to new format
* Tracker errors now change the tracker status
Plugins:
* Fix bug in Blocklist that prevented downloading a new file every X days
GtkUI:
* Sort filenames alphabetically in add torrent dialog
* Fix setting file priorities on folders
* Fix #453 allow showing of text in the toolbar buttons
Deluge 0.9.07 - "1.0.0_RC7" (18 August 2008)
Core:
* Fix loading torrents from state when fastresume file is missing
* Fix UPnP
* Fix to prevent Deluge from segfaulting when trying to autoadd an incomplete torrent file
* Fix #407 possible negative ETA
GtkUI:
* Add 'edit' to edit trackers dialog
* Improve performance of initial torrent list load
* Fix hiding the bottom pane when disabling all the tabs
* Fix not showing new torrents if you don't use the All label first
* Fix size units to be more accurate
* Fix torrentview sorting to be persistent
* Fix not displaying file list when state changes
* Expand root folder in files tab by default
Null:
* Fix #415 crash when using 'config-set' with no parameters
Windows:
* Fix Vista slowness issue
* Fix properly shutting Deluge down when system shuts down
* Fix opening folders/files
Deluge 0.9.06 - "1.0.0_RC6" (13 August 2008)
Core:
* Fix CPU spikes
GtkUI:
* Fix move storage dialog when connected to a remote daemon
Deluge 0.9.05 - "1.0.0_RC5" (04 August 2008)
Core:
* Fix deluged running with ssh X forwarding by removing the Gnome lib import
* Save resume data periodically to help prevent data loss
* Fix queue order shuffling on restart
GtkUI:
* Handle shutting down more cleanly
* Add translators to credits
Plugins:
* Improve the Blocklist plugin preferences page.
Windows:
* Fix drag n' drop support
Deluge 0.9.04 - "1.0.0_RC4" (29 July 2008)
Core:
* Fix building with gcc 4.3
* Fix do not create torrentfiles folder unless 'copy_torrent_file' is True
GtkUI:
* Add drag n' drop support for adding .torrent files
* Double-clicking on host in ConnectionManager now will connect to that host
* Fix selecting torrents when right-clicking on them in torrentview and filestab
* Fix new release check
* Display 'total_wanted' instead of 'total_size' in Size column
* Fix displaying of torrents when language is not English
* Fix the view options to be persistent between sessions
* Fix signalreceiver when switching between daemons
Deluge 0.9.03 - "1.0.0_RC3" (21 July 2008)
Core:
* File progress fixes from libtorrent
* Fix building on FreeBSD
* Fix #350 stop seeds when stop ratio is reached
* Fix #358 properly emit torrent_removed signal when remove_at_ratio happens
UI:
* Default to gtkui when running 'deluge' instead of defaulting to last used.
GtkUI:
* Fix open folder
* Fix #349 tab ordering when hiding/showing
Windows:
* Fix torrent file association and adding files from command line
Plugins:
* Blocklist plugin has been rewritten
Misc:
* Some changes for python 2.6 compatibility
Deluge 0.9.02 - "1.0.0_RC2" (15 July 2008)
Core:
* Fix displaying of file progress
* Fix files to have proper read/write permissions
WebUI:
* Include missing 'classic' template
* Update options tab to include queue settings
Windows:
* Fix displaying of tray icon
* Fix scrolling of tray menu
* Fix hiding of tray icon when shutting down
* Fix tray icon tool tip length to show properly

View File

@ -1,100 +0,0 @@
# Deluge dependencies
The following are required to install and run Deluge. They are separated into
sections to distinguish the precise requirements for each module.
All modules will require the [common](#common) section dependencies.
## Prerequisite
- [Python] _>= 3.6_
## Build
- [setuptools]
- [intltool] - Optional: Desktop file translation for \*nix.
- [closure-compiler] - Minify javascript (alternative is [rjsmin])
## Common
- [Twisted] _>= 17.1_ - Use `TLS` extras for `service_identity` and `idna`.
- [OpenSSL] _>= 1.0.1_
- [pyOpenSSL]
- [rencode] _>= 1.0.2_ - Encoding library.
- [PyXDG] - Access freedesktop.org standards for \*nix.
- [xdg-utils] - Provides xdg-open for \*nix.
- [zope.interface]
- [chardet] - Optional: Encoding detection.
- [setproctitle] - Optional: Renaming processes.
- [Pillow] - Optional: Support for resizing tracker icons.
- [dbus-python] - Optional: Show item location in filemanager.
- [ifaddr] - Optional: Verify network interfaces.
### Linux and BSD
- [distro] - Optional: OS platform information.
### Windows OS
- [pywin32]
- [certifi]
## Core (deluged daemon)
- [libtorrent] _>= 1.2.0_
- [GeoIP] or [pygeoip] - Optional: IP address country lookup. (_Debian: `python-geoip`_)
## GTK UI
- [GTK+] >= 3.10
- [PyGObject]
- [Pycairo]
- [librsvg] _>= 2_
- [libappindicator3] w/GIR - Optional: Ubuntu system tray icon.
### MacOS
- [GtkOSXApplication]
## Web UI
- [mako]
## Plugins
### Notifications
- [pygame] - Optional: Play sounds
- [libnotify] w/GIR - Optional: Desktop popups.
[python]: https://www.python.org/
[setuptools]: https://setuptools.readthedocs.io/en/latest/
[intltool]: https://freedesktop.org/wiki/Software/intltool/
[closure-compiler]: https://developers.google.com/closure/compiler/
[rjsmin]: https://pypi.org/project/rjsmin/
[openssl]: https://www.openssl.org/
[pyopenssl]: https://pyopenssl.org
[twisted]: https://twistedmatrix.com
[pillow]: https://pypi.org/project/Pillow/
[libtorrent]: https://libtorrent.org/
[zope.interface]: https://pypi.org/project/zope.interface/
[distro]: https://github.com/nir0s/distro
[pywin32]: https://github.com/mhammond/pywin32
[certifi]: https://pypi.org/project/certifi/
[dbus-python]: https://pypi.org/project/dbus-python/
[setproctitle]: https://pypi.org/project/setproctitle/
[gtkosxapplication]: https://github.com/jralls/gtk-mac-integration
[chardet]: https://chardet.github.io/
[rencode]: https://github.com/aresch/rencode
[pyxdg]: https://www.freedesktop.org/wiki/Software/pyxdg/
[xdg-utils]: https://www.freedesktop.org/wiki/Software/xdg-utils/
[gtk+]: https://www.gtk.org/
[pycairo]: https://cairographics.org/pycairo/
[pygobject]: https://pygobject.readthedocs.io/en/latest/
[geoip]: https://pypi.org/project/GeoIP/
[mako]: https://www.makotemplates.org/
[pygame]: https://www.pygame.org/
[libnotify]: https://developer.gnome.org/libnotify/
[python-appindicator]: https://packages.ubuntu.com/xenial/python-appindicator
[librsvg]: https://wiki.gnome.org/action/show/Projects/LibRsvg
[ifaddr]: https://pypi.org/project/ifaddr/

13
LICENSE
View File

@ -1,16 +1,3 @@
Deluge is licensed under the GNU General Public License version 3 with the
addition of the following special exception:
In addition, as a special exception, the copyright holders give
permission to link the code of portions of this program with the OpenSSL
library.
You must obey the GNU General Public License in all respects for all of
the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete
this exception statement from your version. If you delete this exception
statement from all source files in the program, then also delete it here.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

View File

@ -1,36 +0,0 @@
include *.md
include AUTHORS
include LICENSE
include RELEASE-VERSION
include msgfmt.py
include minify_web_js.py
include version.py
include gen_web_gettext.py
graft docs/man
graft packaging/systemd
include deluge/i18n/*.po
recursive-exclude deluge/i18n *.mo
graft deluge/plugins
recursive-exclude deluge/plugins create_dev_link.sh *.pyc *.egg
prune deluge/plugins/*/build
prune deluge/plugins/*/*.egg-info
graft deluge/tests/
recursive-exclude deluge/tests *.pyc
graft deluge/ui/data
recursive-exclude deluge/ui/data *.desktop *.xml
graft deluge/ui/gtk3/glade
include deluge/ui/web/index.html
include deluge/ui/web/css/*.css
include deluge/ui/web/js/*.js
graft deluge/ui/web/js/deluge-all/
graft deluge/ui/web/js/extjs/
graft deluge/ui/web/themes
graft deluge/ui/web/render
graft deluge/ui/web/icons
graft deluge/ui/web/images

93
README Normal file
View File

@ -0,0 +1,93 @@
==========================
Deluge BitTorrent Client
==========================
Authors:
Andrew Resch
Marcos Pinto
Martijn Voncken
Sadrul Habib Chowdhury
Homepage: http://deluge-torrent.org
==========================
License
==========================
Deluge is under the GNU GPLv3 license.
Icon data/pixmaps/deluge.svg and derivatives in data/icons are copyright
Andrew Wedderburn and are under the GNU GPLv3.
All other icons in data/pixmaps are copyright Andrew Resch and are under
the GNU GPLv3.
==========================
Contact/Support:
==========================
We have two options available for support:
Our Forum, at http://forum.deluge-torrent.org
or
Our IRC Channel, at #deluge on Freenode
==========================
Installation Instructions:
==========================
First, make sure you have the proper build dependencies installed. On a normal
Debian or Ubuntu system:
sudo apt-get install g++ make python-all-dev python-all python-dbus \
python-gtk2 python-notify librsvg2-common python-xdg python-support \
subversion libboost-dev libboost-python-dev libboost-iostreams-dev \
libboost-thread-dev libboost-date-time-dev libboost-filesystem-dev \
libboost-serialization-dev libssl-dev zlib1g-dev python-setuptools
The names of the packages may vary depending on your OS / distro.
Once you have the needed libraries installed, build and install by running:
$ python setup.py build
$ sudo python setup.py install
==========================
FAQ
==========================
How to start the various user-interfaces
Gtk:
deluge --ui gtk
Console:
deluge --ui null
Web:
deluge --ui web
Go to http://localhost:8112/ default-password = "deluge"
I started "deluge" but i don't see the gtk-ui
The deluge command remembers the last interface it started. Be explicit and type one of the full "deluge -u <interface>" commands listed above.
Why is deluge still listed in my system tray even after I close it ?
You closed the gtk user-interface but you did not close the daemon. Choose "Quit & Shutdown Daemon" to close both Daemon and gtk-ui.
How do I start the daemon ?
deluged
How do I start the daemon with logging to console ?
deluged -d
I can't connect to the daemon from another machine
* Configure the daemon to allow remote connections
* Restart the daemon.
Note: do not do this on a public ip , use the webui for unsecure networks.
I upgraded from 0.5 and plugin x is missing
1.0 is a rewrite, all old 0.5 plugins have to be rewritten.

View File

@ -1,71 +0,0 @@
# Deluge BitTorrent Client
[![build-status]][github-ci] [![docs-status]][rtd-deluge]
Deluge is a BitTorrent client that utilizes a daemon/client model.
It has various user interfaces available such as the GTK-UI, Web-UI and
Console-UI. It uses [libtorrent][lt] at its core to handle the BitTorrent
protocol.
## Install
From [PyPi](https://pypi.org/project/deluge):
pip install deluge
with all optional dependencies:
pip install deluge[all]
From source code:
pip install .
with all optional dependencies:
pip install .[all]
See [DEPENDS](DEPENDS.md) and [Installing/Source] for dependency details.
## Usage
The various user-interfaces and Deluge daemon can be started with the following commands.
Use the `--help` option for further command options.
### Gtk UI
`deluge` or `deluge-gtk`
### Console UI
`deluge-console`
### Web UI
`deluge-web`
Open http://localhost:8112 with default password `deluge`.
### Daemon
`deluged`
See the [Thinclient guide] to connect to the daemon from another computer.
## Contact
- [Homepage](https://deluge-torrent.org)
- [User guide][user guide]
- [Forum](https://forum.deluge-torrent.org)
- [IRC Libera.Chat #deluge](irc://irc.libera.chat/deluge)
- [Discord](https://discord.gg/nwaHSE6tqn)
[user guide]: https://dev.deluge-torrent.org/wiki/UserGuide
[thinclient guide]: https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient
[installing/source]: https://dev.deluge-torrent.org/wiki/Installing/Source
[build-status]: https://github.com/deluge-torrent/deluge/actions/workflows/ci.yml/badge.svg?branch=develop "CI"
[github-ci]: https://github.com/deluge-torrent/deluge/actions/workflows/ci.yml
[docs-status]: https://readthedocs.org/projects/deluge/badge/?version=latest
[rtd-deluge]: https://deluge.readthedocs.io/en/latest/?badge=latest "Documentation Status"
[lt]: https://libtorrent.org

6
createicons.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
for size in 16 22 24 32 36 48 64 72 96 128 192 256; do mkdir -p deluge/data/\
icons/hicolor/${size}x${size}/apps; rsvg-convert -w ${size} -h ${size} \
-o deluge/data/icons/hicolor/${size}x${size}/apps/deluge.png deluge/data/pixmaps\
/deluge.svg; mkdir -p deluge/data/icons/scalable/apps/; cp deluge/data/pixmaps/\
deluge.svg deluge/data/icons/scalable/apps/deluge.svg; done

5
debian/changelog vendored Normal file
View File

@ -0,0 +1,5 @@
deluge-torrent (1.1.2-1) unstable; urgency=low
* 1.1.2 final
-- Andrew Resch (andar) <andrewresch@gmail.com> Sat, 31 Jan 2008 16:31:14 -0800

5
debian/changelog.debian-lenny vendored Normal file
View File

@ -0,0 +1,5 @@
deluge-torrent (0.6.0-svn3235-1) lenny; urgency=low
* Daily Build
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800

5
debian/changelog.debian-sid vendored Normal file
View File

@ -0,0 +1,5 @@
deluge-torrent (0.6.0-svn3235-1) unstable; urgency=low
* Daily Build
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800

5
debian/changelog.ubuntu-gutsy vendored Normal file
View File

@ -0,0 +1,5 @@
deluge-torrent (0.6.0-svn3235-1) gutsy; urgency=low
* Daily Build
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800

5
debian/changelog.ubuntu-hardy vendored Normal file
View File

@ -0,0 +1,5 @@
deluge-torrent (0.6.0-svn3235-1) hardy; urgency=low
* Daily Build
-- Andrew Resch (andar) <andrewresch@gmail.com> Tue, 17 Jun 2008 16:31:14 -0800

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
5

21
debian/control vendored Normal file
View File

@ -0,0 +1,21 @@
Source: deluge-torrent
Section: net
Priority: optional
Maintainer: Andrew Resch (andar) <andrewresch@gmail.com>
Build-Depends: debhelper (>= 5.0.37.2), python-all-dev (>= 2.3.5-11), python-all, python-support (>= 0.5.3), libboost-dev (>= 1.34.1), libboost-thread-dev (>= 1.34.1), libboost-date-time-dev (>= 1.34.1), libboost-filesystem-dev (>= 1.34.1), libboost-python-dev (>= 1.34.1), libboost-iostreams-dev (>= 1.34.1), zlib1g-dev, libssl-dev, dpatch, python-setuptools
Standards-Version: 3.7.2
Package: deluge-torrent
Architecture: any
Depends: ${shlibs:Depends}, ${python:Depends}, python-gtk2, python-glade2, python-xdg, python-notify, notification-daemon | notification-daemon-xfce, python-dbus, librsvg2-common, python-pyopenssl, python-setuptools, python-pkg-resources
Conflicts: deluge-torrent-common
Replaces: deluge-torrent-common
Description: A Bittorrent client written in Python/PyGTK
Deluge is a Bittorrent client, created using Python and GTK+.
.
Deluge is intended to bring a native, full-featured client to Linux GTK
desktop environments such as Gnome and XFCE.
.
It uses Rasterbar's version of libtorrent.
.
Homepage: http://www.deluge-torrent.org/

19
debian/control.debian-lenny vendored Normal file
View File

@ -0,0 +1,19 @@
Source: deluge-torrent
Section: net
Priority: optional
Maintainer: Andrew Resch (andar) <andrewresch@gmail.com>
Build-Depends: debhelper (>= 5.0.37.2), python-all-dev (>= 2.3.5-11), python-all, python-support (>= 0.5.3), libboost-dev (>= 1.33.1), libboost-thread-dev (>= 1.33.1), libboost-date-time-dev (>= 1.33.1), libboost-filesystem-dev (>= 1.33.1), libboost-serialization-dev (>= 1.33.1), libboost-program-options-dev (>= 1.33.1), libboost-regex-dev (>= 1.33.1), libboost-python-dev (>= 1.33.1), zlib1g-dev, libssl-dev, dpatch, python-setuptools
Standards-Version: 3.7.2
Package: deluge-torrent
Architecture: any
Depends: ${shlibs:Depends}, ${python:Depends}, python-gtk2, python-glade2, python-xdg, python-notify, notification-daemon | notification-daemon-xfce, python-dbus, librsvg2-common, python-pyopenssl, python-setuptools
Description: A Bittorrent client written in Python/PyGTK
Deluge is a Bittorrent client, created using Python and GTK+.
.
Deluge is intended to bring a native, full-featured client to Linux GTK
desktop environments such as Gnome and XFCE.
.
It uses Rasterbar's version of libtorrent.
.
Homepage: http://www.deluge-torrent.org/

19
debian/control.debian-sid vendored Normal file
View File

@ -0,0 +1,19 @@
Source: deluge-torrent
Section: net
Priority: optional
Maintainer: Andrew Resch (andar) <andrewresch@gmail.com>
Build-Depends: debhelper (>= 5.0.37.2), python-all-dev (>= 2.3.5-11), python-all, python-support (>= 0.5.3), libboost1.36-dev (>= 1.36), libboost-thread1.36-dev (>= 1.36), libboost-date-time1.36-dev (>= 1.36), libboost-filesystem1.36-dev (>= 1.36), libboost-serialization1.36-dev (>= 1.36), libboost-program-options1.36-dev (>= 1.36), libboost-regex1.36-dev (>= 1.36), libboost-python1.36-dev (>= 1.36), zlib1g-dev, libssl-dev, dpatch, python-setuptools
Standards-Version: 3.7.2
Package: deluge-torrent
Architecture: any
Depends: ${shlibs:Depends}, ${python:Depends}, python-gtk2, python-glade2, python-xdg, python-notify, notification-daemon | notification-daemon-xfce, python-dbus, librsvg2-common, python-pyopenssl, python-setuptools
Description: A Bittorrent client written in Python/PyGTK
Deluge is a Bittorrent client, created using Python and GTK+.
.
Deluge is intended to bring a native, full-featured client to Linux GTK
desktop environments such as Gnome and XFCE.
.
It uses Rasterbar's version of libtorrent.
.
Homepage: http://www.deluge-torrent.org/

99
debian/copyright vendored Normal file
View File

@ -0,0 +1,99 @@
This package was debianized by Marcos Pinto (markybob) <markybob@gmail.com> on
Fri, 31 Jul 2008 22:03:13 +0100.
It was downloaded from http://www.deluge-torrent.org/
Upstream Authors & Copyright:
Andrew Resch
Marcos Pinto
Sadrul Habib Chowdhury
Martijn Voncken
License:
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this package; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
In addition, as a special exception, the copyright holders give
permission to link the code of portions of this program with the OpenSSL
library.
You must obey the GNU General Public License in all respects for all of
the code used other than OpenSSL. If you modify file(s) with this
exception, you may extend this exception to your version of the file(s),
but you are not obligated to do so. If you do not wish to do so, delete
this exception statement from your version. If you delete this exception
statement from all source files in the program, then also delete it here.
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/GPL'.
libtorrent is (C) 2003-2008 Arvid Norberg arvid@cs.umu.se and its
python bindings were initially written by Daniel Wallin in 2006.
It is distributed unders terms of the BSD License below:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"libtorrent/include/libtorrent/asio*" are (C) 2003-2008 Christopher
M. Kohlhoff <chris@kohlhoff.com> and distributed under terms of the Boost
Software License, Version 1.0 :
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"deluge/i18n/*" are (C) 2006 Rosetta Contributors and Canonical Ltd 2006 and distributed
under the same license as the deluge software.
The Debian packaging is (C) 2006-2008, Marcos Pinto (markybob)
<markybob@gmail.com> and is licensed under GPL3, see above.

2
debian/manpages vendored Normal file
View File

@ -0,0 +1,2 @@
deluge/docs/man/deluge.1
deluge/docs/man/deluged.1

3
debian/menu vendored Normal file
View File

@ -0,0 +1,3 @@
?package(deluge-torrent): needs="X11" section="Applications/Network/File Transfer" \
title="Deluge BitTorrent Client" longtitle="Bittorrent client written in Python/PyGTK" \
command="/usr/bin/deluge" icon="/usr/share/pixmaps/deluge.png"

1
debian/pyversions vendored Normal file
View File

@ -0,0 +1 @@
2.5

1
debian/pyversions.ubuntu-gutsy vendored Normal file
View File

@ -0,0 +1 @@
2.5

1
debian/pyversions.ubuntu-hardy vendored Normal file
View File

@ -0,0 +1 @@
2.5

75
debian/rules vendored Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# Dpatch targets
include /usr/share/dpatch/dpatch.make
# Available python (using debian/pyversions) and destdir
PYVERS = 2.5
DESTDIR = $(CURDIR)/debian/deluge-torrent
# We need to known the target arch to enable/disable amd64 hack
ARCH = $(shell dpkg-architecture -qDEB_BUILD_ARCH_CPU)
ARCH64 = ia64 amd64 alpha kfreebsd-amd64 ppc64
CFLAGS = -Wall -g
ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
CFLAGS += -O0
else
CFLAGS += -O2
endif
# python-libtorrent need to define AMD64 to work fine on a 64 bits system
ifneq (,$(findstring $(ARCH),$(ARCH64)))
CFLAGS += -DAMD64
endif
build: patch-stamp $(PYVERS:%=build-stamp%)
build-stamp%: patch-stamp
dh_testdir
CFLAGS="$(CFLAGS)" python$* setup.py build
touch $@
clean: unpatch
dh_testdir
dh_testroot
rm -rf build/
find . -name \*.pyc | xargs rm -f
rm -rf build-stamp*
dh_clean
install: build install-prereq $(PYVERS:%=install-%) install-finish
install-prereq:
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
install-%:
python$* setup.py install --root=$(DESTDIR) --prefix=/usr --no-compile
install-finish:
# Desktop menu
rm -rf $(DESTDIR)/usr/share/applications
install -D -m644 $(CURDIR)/deluge/data/share/applications/deluge.desktop $(DESTDIR)/usr/share/applications/deluge.desktop
binary-indep: build install
binary-arch: build install
dh_testdir
dh_testroot
dh_installchangelogs
dh_installdocs
dh_installmenu
dh_strip
dh_compress
dh_fixperms
dh_pysupport
dh_desktop
dh_installdeb
dh_shlibdeps
dh_gencontrol
dh_md5sums
dh_builddeb
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install

View File

@ -0,0 +1,595 @@
"""Simple XML-RPC Server.
This module can be used to create simple XML-RPC servers
by creating a server and either installing functions, a
class instance, or by extending the SimpleXMLRPCServer
class.
It can also be used to handle XML-RPC requests in a CGI
environment using CGIXMLRPCRequestHandler.
A list of possible usage patterns follows:
1. Install functions:
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add')
server.serve_forever()
2. Install an instance:
class MyFuncs:
def __init__(self):
# make all of the string functions available through
# string.func_name
import string
self.string = string
def _listMethods(self):
# implement this method so that system.listMethods
# knows to advertise the strings methods
return list_public_methods(self) + \
['string.' + method for method in list_public_methods(self.string)]
def pow(self, x, y): return pow(x, y)
def add(self, x, y) : return x + y
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_introspection_functions()
server.register_instance(MyFuncs())
server.serve_forever()
3. Install an instance with custom dispatch method:
class Math:
def _listMethods(self):
# this method must be present for system.listMethods
# to work
return ['add', 'pow']
def _methodHelp(self, method):
# this method must be present for system.methodHelp
# to work
if method == 'add':
return "add(2,3) => 5"
elif method == 'pow':
return "pow(x, y[, z]) => number"
else:
# By convention, return empty
# string if no help is available
return ""
def _dispatch(self, method, params):
if method == 'pow':
return pow(*params)
elif method == 'add':
return params[0] + params[1]
else:
raise 'bad method'
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_introspection_functions()
server.register_instance(Math())
server.serve_forever()
4. Subclass SimpleXMLRPCServer:
class MathServer(SimpleXMLRPCServer):
def _dispatch(self, method, params):
try:
# We are forcing the 'export_' prefix on methods that are
# callable through XML-RPC to prevent potential security
# problems
func = getattr(self, 'export_' + method)
except AttributeError:
raise Exception('method "%s" is not supported' % method)
else:
return func(*params)
def export_add(self, x, y):
return x + y
server = MathServer(("localhost", 8000))
server.serve_forever()
5. CGI script:
server = CGIXMLRPCRequestHandler()
server.register_function(pow)
server.handle_request()
"""
# Written by Brian Quinlan (brian@sweetapp.com).
# Based on code written by Fredrik Lundh.
import xmlrpclib
from xmlrpclib import Fault
import SocketServer
import BaseHTTPServer
import sys
import os
try:
import fcntl
except ImportError:
fcntl = None
def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
"""resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
Resolves a dotted attribute name to an object. Raises
an AttributeError if any attribute in the chain starts with a '_'.
If the optional allow_dotted_names argument is false, dots are not
supported and this function operates similar to getattr(obj, attr).
"""
if allow_dotted_names:
attrs = attr.split('.')
else:
attrs = [attr]
for i in attrs:
if i.startswith('_'):
raise AttributeError(
'attempt to access private attribute "%s"' % i
)
else:
obj = getattr(obj,i)
return obj
def list_public_methods(obj):
"""Returns a list of attribute strings, found in the specified
object, which represent callable attributes"""
return [member for member in dir(obj)
if not member.startswith('_') and
callable(getattr(obj, member))]
def remove_duplicates(lst):
"""remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
Returns a copy of a list without duplicates. Every list
item must be hashable and the order of the items in the
resulting list is not defined.
"""
u = {}
for x in lst:
u[x] = 1
return u.keys()
class SimpleXMLRPCDispatcher:
"""Mix-in class that dispatches XML-RPC requests.
This class is used to register XML-RPC method handlers
and then to dispatch them. There should never be any
reason to instantiate this class directly.
"""
def __init__(self, allow_none, encoding):
self.funcs = {}
self.instance = None
self.allow_none = allow_none
self.encoding = encoding
def register_instance(self, instance, allow_dotted_names=False):
"""Registers an instance to respond to XML-RPC requests.
Only one instance can be installed at a time.
If the registered instance has a _dispatch method then that
method will be called with the name of the XML-RPC method and
its parameters as a tuple
e.g. instance._dispatch('add',(2,3))
If the registered instance does not have a _dispatch method
then the instance will be searched to find a matching method
and, if found, will be called. Methods beginning with an '_'
are considered private and will not be called by
SimpleXMLRPCServer.
If a registered function matches a XML-RPC request, then it
will be called instead of the registered instance.
If the optional allow_dotted_names argument is true and the
instance does not have a _dispatch method, method names
containing dots are supported and resolved, as long as none of
the name segments start with an '_'.
*** SECURITY WARNING: ***
Enabling the allow_dotted_names options allows intruders
to access your module's global variables and may allow
intruders to execute arbitrary code on your machine. Only
use this option on a secure, closed network.
"""
self.instance = instance
self.allow_dotted_names = allow_dotted_names
def register_function(self, function, name = None):
"""Registers a function to respond to XML-RPC requests.
The optional name argument can be used to set a Unicode name
for the function.
"""
if name is None:
name = function.__name__
self.funcs[name] = function
def register_introspection_functions(self):
"""Registers the XML-RPC introspection methods in the system
namespace.
see http://xmlrpc.usefulinc.com/doc/reserved.html
"""
self.funcs.update({'system.listMethods' : self.system_listMethods,
'system.methodSignature' : self.system_methodSignature,
'system.methodHelp' : self.system_methodHelp})
def register_multicall_functions(self):
"""Registers the XML-RPC multicall method in the system
namespace.
see http://www.xmlrpc.com/discuss/msgReader$1208"""
self.funcs.update({'system.multicall' : self.system_multicall})
def _marshaled_dispatch(self, data, dispatch_method = None):
"""Dispatches an XML-RPC method from marshalled (XML) data.
XML-RPC methods are dispatched from the marshalled (XML) data
using the _dispatch method and the result is returned as
marshalled data. For backwards compatibility, a dispatch
function can be provided as an argument (see comment in
SimpleXMLRPCRequestHandler.do_POST) but overriding the
existing method through subclassing is the prefered means
of changing method dispatch behavior.
"""
try:
params, method = xmlrpclib.loads(data)
# generate response
if dispatch_method is not None:
response = dispatch_method(method, params)
else:
response = self._dispatch(method, params)
# wrap response in a singleton tuple
response = (response,)
response = xmlrpclib.dumps(response, methodresponse=1,
allow_none=self.allow_none, encoding=self.encoding)
except Fault, fault:
response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
encoding=self.encoding)
except:
# report exception back to server
response = xmlrpclib.dumps(
xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)),
encoding=self.encoding, allow_none=self.allow_none,
)
return response
def system_listMethods(self):
"""system.listMethods() => ['add', 'subtract', 'multiple']
Returns a list of the methods supported by the server."""
methods = self.funcs.keys()
if self.instance is not None:
# Instance can implement _listMethod to return a list of
# methods
if hasattr(self.instance, '_listMethods'):
methods = remove_duplicates(
methods + self.instance._listMethods()
)
# if the instance has a _dispatch method then we
# don't have enough information to provide a list
# of methods
elif not hasattr(self.instance, '_dispatch'):
methods = remove_duplicates(
methods + list_public_methods(self.instance)
)
methods.sort()
return methods
def system_methodSignature(self, method_name):
"""system.methodSignature('add') => [double, int, int]
Returns a list describing the signature of the method. In the
above example, the add method takes two integers as arguments
and returns a double result.
This server does NOT support system.methodSignature."""
# See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
return 'signatures not supported'
def system_methodHelp(self, method_name):
"""system.methodHelp('add') => "Adds two integers together"
Returns a string containing documentation for the specified method."""
method = None
if self.funcs.has_key(method_name):
method = self.funcs[method_name]
elif self.instance is not None:
# Instance can implement _methodHelp to return help for a method
if hasattr(self.instance, '_methodHelp'):
return self.instance._methodHelp(method_name)
# if the instance has a _dispatch method then we
# don't have enough information to provide help
elif not hasattr(self.instance, '_dispatch'):
try:
method = resolve_dotted_attribute(
self.instance,
method_name,
self.allow_dotted_names
)
except AttributeError:
pass
# Note that we aren't checking that the method actually
# be a callable object of some kind
if method is None:
return ""
else:
import pydoc
return pydoc.getdoc(method)
def system_multicall(self, call_list):
"""system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
[[4], ...]
Allows the caller to package multiple XML-RPC calls into a single
request.
See http://www.xmlrpc.com/discuss/msgReader$1208
"""
results = []
for call in call_list:
method_name = call['methodName']
params = call['params']
try:
# XXX A marshalling error in any response will fail the entire
# multicall. If someone cares they should fix this.
results.append([self._dispatch(method_name, params)])
except Fault, fault:
results.append(
{'faultCode' : fault.faultCode,
'faultString' : fault.faultString}
)
except:
results.append(
{'faultCode' : 1,
'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
)
return results
def _dispatch(self, method, params):
"""Dispatches the XML-RPC method.
XML-RPC calls are forwarded to a registered function that
matches the called XML-RPC method name. If no such function
exists then the call is forwarded to the registered instance,
if available.
If the registered instance has a _dispatch method then that
method will be called with the name of the XML-RPC method and
its parameters as a tuple
e.g. instance._dispatch('add',(2,3))
If the registered instance does not have a _dispatch method
then the instance will be searched to find a matching method
and, if found, will be called.
Methods beginning with an '_' are considered private and will
not be called.
"""
func = None
try:
# check to see if a matching function has been registered
func = self.funcs[method]
except KeyError:
if self.instance is not None:
# check for a _dispatch method
if hasattr(self.instance, '_dispatch'):
return self.instance._dispatch(method, params)
else:
# call instance method directly
try:
func = resolve_dotted_attribute(
self.instance,
method,
self.allow_dotted_names
)
except AttributeError:
pass
if func is not None:
return func(*params)
else:
raise Exception('method "%s" is not supported' % method)
class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""Simple XML-RPC request handler class.
Handles all HTTP POST requests and attempts to decode them as
XML-RPC requests.
"""
# Class attribute listing the accessible path components;
# paths not on this list will result in a 404 error.
rpc_paths = ('/', '/RPC2')
def is_rpc_path_valid(self):
if self.rpc_paths:
return self.path in self.rpc_paths
else:
# If .rpc_paths is empty, just assume all paths are legal
return True
def do_POST(self):
"""Handles the HTTP POST request.
Attempts to interpret all HTTP POST requests as XML-RPC calls,
which are forwarded to the server's _dispatch method for handling.
"""
# Check that the path is legal
if not self.is_rpc_path_valid():
self.report_404()
return
try:
# Get arguments by reading body of request.
# We read this in chunks to avoid straining
# socket.read(); around the 10 or 15Mb mark, some platforms
# begin to have problems (bug #792570).
max_chunk_size = 10*1024*1024
size_remaining = int(self.headers["content-length"])
L = []
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
L.append(self.rfile.read(chunk_size))
size_remaining -= len(L[-1])
data = ''.join(L)
# In previous versions of SimpleXMLRPCServer, _dispatch
# could be overridden in this class, instead of in
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
# check to see if a subclass implements _dispatch and dispatch
# using that method if present.
response = self.server._marshaled_dispatch(
data, getattr(self, '_dispatch', None)
)
except: # This should only happen if the module is buggy
# internal error, report as HTTP server error
self.send_response(500)
self.end_headers()
else:
# got a valid XML RPC response
self.send_response(200)
self.send_header("Content-type", "text/xml")
self.send_header("Content-length", str(len(response)))
self.end_headers()
self.wfile.write(response)
# shut down the connection
self.wfile.flush()
self.connection.shutdown(1)
def report_404 (self):
# Report a 404 error
self.send_response(404)
response = 'No such page'
self.send_header("Content-type", "text/plain")
self.send_header("Content-length", str(len(response)))
self.end_headers()
self.wfile.write(response)
# shut down the connection
self.wfile.flush()
self.connection.shutdown(1)
def log_request(self, code='-', size='-'):
"""Selectively log an accepted request."""
if self.server.logRequests:
BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
class SimpleXMLRPCServer(SocketServer.TCPServer,
SimpleXMLRPCDispatcher):
"""Simple XML-RPC server.
Simple XML-RPC server that allows functions and a single instance
to be installed to handle requests. The default implementation
attempts to dispatch XML-RPC calls to the functions or instance
installed in the server. Override the _dispatch method inhereted
from SimpleXMLRPCDispatcher to change this behavior.
"""
allow_reuse_address = True
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None):
self.logRequests = logRequests
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
SocketServer.TCPServer.__init__(self, addr, requestHandler)
# [Bug #1222790] If possible, set close-on-exec flag; if a
# method spawns a subprocess, the subprocess shouldn't have
# the listening socket open.
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
"""Simple handler for XML-RPC data passed through CGI."""
def __init__(self, allow_none=False, encoding=None):
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
def handle_xmlrpc(self, request_text):
"""Handle a single XML-RPC request"""
response = self._marshaled_dispatch(request_text)
print('Content-Type: text/xml')
print('Content-Length: %d' % len(response))
print(
sys.stdout.write(response))
def handle_get(self):
"""Handle a single HTTP GET request.
Default implementation indicates an error because
XML-RPC uses the POST method.
"""
code = 400
message, explain = \
BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
{
'code' : code,
'message' : message,
'explain' : explain
}
print('Status: %d %s' % (code, message))
print('Content-Type: text/html')
print('Content-Length: %d' % len(response))
print(
sys.stdout.write(response))
def handle_request(self, request_text = None):
"""Handle a single XML-RPC request passed through a CGI post method.
If no XML data is given then it is read from stdin. The resulting
XML-RPC response is printed to stdout along with the correct HTTP
headers.
"""
if request_text is None and \
os.environ.get('REQUEST_METHOD', None) == 'GET':
self.handle_get()
else:
# POST data is normally available through stdin
if request_text is None:
request_text = sys.stdin.read()
self.handle_xmlrpc(request_text)
if __name__ == '__main__':
print('Running XML-RPC server on port 8000')
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add')
server.serve_forever()

View File

@ -1,35 +0,0 @@
#
# Copyright (C) 2009 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.
#
"""
This module is used to handle the importing of libtorrent and also controls
the minimum versions of libtorrent that this version of Deluge supports.
Example:
>>> from deluge._libtorrent import lt
"""
from deluge.common import VersionSplit, get_version
from deluge.error import LibtorrentImportError
try:
import deluge.libtorrent as lt
except ImportError:
try:
import libtorrent as lt
except ImportError as ex:
raise LibtorrentImportError('No libtorrent library found: %s' % (ex))
REQUIRED_VERSION = '1.2.0.0'
LT_VERSION = lt.__version__
if VersionSplit(LT_VERSION) < VersionSplit(REQUIRED_VERSION):
raise LibtorrentImportError(
f'Deluge {get_version()} requires libtorrent >= {REQUIRED_VERSION}'
)

View File

@ -1,384 +0,0 @@
#
# 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.
#
import argparse
import logging
import os
import platform
import sys
import textwrap
import deluge.log
from deluge import common
from deluge.configmanager import get_config_dir, set_config_dir
def find_subcommand(self, args=None, sys_argv=True):
"""Find if a subcommand has been supplied.
Args:
args (list, optional): The argument list to search through.
sys_argv (bool): Use sys.argv[1:] if args is None.
Returns:
int: Index of the subcommand or '-1' if none found.
"""
subcommand_found = -1
if args is None:
args = sys.argv[1:] if sys_argv is None else []
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map:
if sp_name in args:
subcommand_found = args.index(sp_name)
return subcommand_found
def set_default_subparser(self, name, abort_opts=None):
"""Sets the default argparse subparser.
Args:
name (str): The name of the default subparser.
abort_opts (list): The arguments to test for in case no subcommand is found.
If any of the values are found, the default subparser will
not be inserted into sys.argv.
Returns:
list: The arguments found in sys.argv if no subcommand found, else None
"""
found_abort_opts = []
abort_opts = [] if abort_opts is None else abort_opts
test_args = sys.argv[1:]
subparser_found = self.find_subcommand(args=test_args)
for i, arg in enumerate(test_args):
if subparser_found == i:
break
if arg in abort_opts:
found_abort_opts.append(arg)
if subparser_found == -1:
if found_abort_opts:
# Found one or more of arguments in abort_opts
return found_abort_opts
# insert default in first position, this implies no
# global options without a sub_parsers specified
sys.argv.insert(1, name)
return None
argparse.ArgumentParser.find_subcommand = find_subcommand
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def _get_version_detail():
version_str = '%s\n' % (common.get_version())
try:
from deluge._libtorrent import LT_VERSION
version_str += 'libtorrent: %s\n' % LT_VERSION
except ImportError:
pass
version_str += 'Python: %s\n' % platform.python_version()
version_str += f'OS: {platform.system()} {common.get_os_version()}\n'
return version_str
class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
"""Help message formatter which retains formatting of all help text."""
def _split_lines(self, text, width):
"""
Do not remove whitespaces in string but still wrap text to max width.
Instead of passing the entire text to textwrap.wrap, split and pass each
line instead. This way list formatting is not mangled by textwrap.wrap.
"""
wrapped_lines = []
for line in text.splitlines():
wrapped_lines.extend(textwrap.wrap(line, width, subsequent_indent=' '))
return wrapped_lines
def _format_action_invocation(self, action):
"""
Combines the options with comma and displays the argument
value only once instead of after both options.
Instead of: -s <arg>, --long-opt <arg>
Show : -s, --long-opt <arg>
"""
if not action.option_strings:
(metavar,) = self._metavar_formatter(action, action.dest)(1)
return metavar
else:
parts = []
# if the Optional doesn't take a value, format is:
# -s, --long
if action.nargs == 0:
parts.extend(action.option_strings)
# if the Optional takes a value, format is:
# -s, --long ARGS
else:
default = action.dest.upper()
args_string = self._format_args(action, default)
opt = ', '.join(action.option_strings)
parts.append(f'{opt} {args_string}')
return ', '.join(parts)
class HelpAction(argparse._HelpAction):
def __call__(self, parser, namespace, values, option_string=None):
if hasattr(parser, 'subparser'):
subparser = getattr(parser, 'subparser')
subparser.print_help()
else:
parser.print_help()
parser.exit()
class ArgParserBase(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
if 'formatter_class' not in kwargs:
kwargs['formatter_class'] = lambda prog: DelugeTextHelpFormatter(
prog, max_help_position=33, width=90
)
kwargs['add_help'] = kwargs.get('add_help', False)
common_help = kwargs.pop('common_help', True)
self.log_stream = sys.stdout
if 'log_stream' in kwargs:
self.log_stream = kwargs['log_stream']
del kwargs['log_stream']
super().__init__(*args, **kwargs)
self.common_setup = False
self.process_arg_group = False
self.group = self.add_argument_group(_('Common Options'))
if common_help:
self.group.add_argument(
'-h', '--help', action=HelpAction, help=_('Print this help message')
)
self.group.add_argument(
'-V',
'--version',
action='version',
version='%(prog)s ' + _get_version_detail(),
help=_('Print version information'),
)
self.group.add_argument(
'-v',
action='version',
version='%(prog)s ' + _get_version_detail(),
help=argparse.SUPPRESS,
) # Deprecated arg
self.group.add_argument(
'-c',
'--config',
metavar='<config>',
help=_('Set the config directory path'),
)
self.group.add_argument(
'-l',
'--logfile',
metavar='<logfile>',
help=_('Output to specified logfile instead of stdout'),
)
self.group.add_argument(
'-L',
'--loglevel',
choices=[level for k in deluge.log.levels for level in (k, k.upper())],
help=_('Set the log level (none, error, warning, info, debug)'),
metavar='<level>',
)
self.group.add_argument(
'--logrotate',
nargs='?',
const='2M',
metavar='<max-size>',
help=_(
'Enable logfile rotation, with optional maximum logfile size, '
'default: %(const)s (Logfile rotation count is 5)'
),
)
self.group.add_argument(
'-q',
'--quiet',
action='store_true',
help=_('Quieten logging output (Same as `--loglevel none`)'),
)
self.group.add_argument(
'--profile',
metavar='<profile-file>',
nargs='?',
default=False,
help=_(
'Profile %(prog)s with cProfile. Outputs to stdout '
'unless a filename is specified'
),
)
def parse_args(self, args=None):
"""Parse UI arguments and handle common and process group options.
Notes:
Unknown arguments results in usage text printed and system exit.
Args:
args (list, optional): The arguments to parse.
Returns:
argparse.Namespace: The parsed arguments.
"""
options = super().parse_args(args=args)
return self._handle_ui_options(options)
def parse_known_ui_args(self, args, withhold=None):
"""Parse UI arguments and handle common and process group options without error.
Args:
args (list): The arguments to parse.
withhold (list): Values to ignore in the args list.
Returns:
argparse.Namespace: The parsed arguments.
"""
if withhold:
args = [a for a in args if a not in withhold]
options, remaining = super().parse_known_args(args=args)
options.remaining = remaining
# Handle common and process group options
return self._handle_ui_options(options)
def _handle_ui_options(self, options):
"""Handle UI common and process group options.
Args:
options (argparse.Namespace): The parsed options.
Returns:
argparse.Namespace: The parsed options.
"""
if not self.common_setup:
self.common_setup = True
# Setup the logger
if options.quiet:
options.loglevel = 'none'
if options.loglevel:
options.loglevel = options.loglevel.lower()
logfile_mode = 'w'
logrotate = options.logrotate
if options.logrotate:
logfile_mode = 'a'
logrotate = common.parse_human_size(options.logrotate)
# Setup the logger
deluge.log.setup_logger(
level=options.loglevel,
filename=options.logfile,
filemode=logfile_mode,
logrotate=logrotate,
output_stream=self.log_stream,
)
if options.config:
if not set_config_dir(options.config):
log = logging.getLogger(__name__)
log.error('There was an error setting the config dir! Exiting..')
sys.exit(1)
else:
if not os.path.exists(common.get_default_config_dir()):
os.makedirs(common.get_default_config_dir())
if self.process_arg_group:
self.process_arg_group = False
# If donotdaemonize is set, skip process forking.
if not (common.windows_check() or options.donotdaemonize):
if os.fork():
os._exit(0)
os.setsid()
# Do second fork
if os.fork():
os._exit(0)
# Ensure process doesn't keep any directory in use that may prevent a filesystem unmount.
os.chdir(get_config_dir())
# Write pid file before chuid
if options.pidfile:
with open(options.pidfile, 'w') as _file:
_file.write('%d\n' % os.getpid())
if not common.windows_check():
if options.group:
if not options.group.isdigit():
import grp
options.group = grp.getgrnam(options.group)[2]
os.setgid(options.group)
if options.user:
if not options.user.isdigit():
import pwd
options.user = pwd.getpwnam(options.user)[2]
os.setuid(options.user)
return options
def add_process_arg_group(self):
"""Adds a grouping of common process args to control a daemon to the parser"""
self.process_arg_group = True
self.group = self.add_argument_group(_('Process Control Options'))
self.group.add_argument(
'-P',
'--pidfile',
metavar='<pidfile>',
action='store',
help=_('Pidfile to store the process id'),
)
if not common.windows_check():
self.group.add_argument(
'-d',
'--do-not-daemonize',
dest='donotdaemonize',
action='store_true',
help=_('Do not daemonize (fork) this process'),
)
self.group.add_argument(
'-f',
'--fork',
dest='donotdaemonize',
action='store_false',
help=argparse.SUPPRESS,
) # Deprecated arg
self.group.add_argument(
'-U',
'--user',
metavar='<user>',
action='store',
help=_('Change to this user on startup (Requires root)'),
)
self.group.add_argument(
'-g',
'--group',
metavar='<group>',
action='store',
help=_('Change to this group on startup (Requires root)'),
)

View File

@ -9,140 +9,121 @@
# License.
# Written by Petru Paler
# Updated by Calum Lind to support Python 3.
class BTFailure(Exception):
pass
DICT_DELIM = b'd'
END_DELIM = b'e'
INT_DELIM = b'i'
LIST_DELIM = b'l'
BYTE_SEP = b':'
# Minor modifications made by Andrew Resch to replace the BTFailure errors with Exceptions
def decode_int(x, f):
f += 1
newf = x.index(END_DELIM, f)
newf = x.index('e', f)
n = int(x[f:newf])
if x[f : f + 1] == b'-' and x[f + 1 : f + 2] == b'0':
if x[f] == '-':
if x[f + 1] == '0':
raise ValueError
elif x[f] == '0' and newf != f+1:
raise ValueError
elif x[f : f + 1] == b'0' and newf != f + 1:
raise ValueError
return (n, newf + 1)
return (n, newf+1)
def decode_string(x, f):
colon = x.index(BYTE_SEP, f)
colon = x.index(':', f)
n = int(x[f:colon])
if x[f : f + 1] == b'0' and colon != f + 1:
if x[f] == '0' and colon != f+1:
raise ValueError
colon += 1
return (x[colon : colon + n], colon + n)
return (x[colon:colon+n], colon+n)
def decode_list(x, f):
r, f = [], f + 1
while x[f : f + 1] != END_DELIM:
v, f = decode_func[x[f : f + 1]](x, f)
r, f = [], f+1
while x[f] != 'e':
v, f = decode_func[x[f]](x, f)
r.append(v)
return (r, f + 1)
def decode_dict(x, f):
r, f = {}, f + 1
while x[f : f + 1] != END_DELIM:
r, f = {}, f+1
while x[f] != 'e':
k, f = decode_string(x, f)
r[k], f = decode_func[x[f : f + 1]](x, f)
r[k], f = decode_func[x[f]](x, f)
return (r, f + 1)
decode_func = {}
decode_func[LIST_DELIM] = decode_list
decode_func[DICT_DELIM] = decode_dict
decode_func[INT_DELIM] = decode_int
decode_func[b'0'] = decode_string
decode_func[b'1'] = decode_string
decode_func[b'2'] = decode_string
decode_func[b'3'] = decode_string
decode_func[b'4'] = decode_string
decode_func[b'5'] = decode_string
decode_func[b'6'] = decode_string
decode_func[b'7'] = decode_string
decode_func[b'8'] = decode_string
decode_func[b'9'] = decode_string
decode_func['l'] = decode_list
decode_func['d'] = decode_dict
decode_func['i'] = decode_int
decode_func['0'] = decode_string
decode_func['1'] = decode_string
decode_func['2'] = decode_string
decode_func['3'] = decode_string
decode_func['4'] = decode_string
decode_func['5'] = decode_string
decode_func['6'] = decode_string
decode_func['7'] = decode_string
decode_func['8'] = decode_string
decode_func['9'] = decode_string
def bdecode(x):
try:
r, __ = decode_func[x[0:1]](x, 0)
except (LookupError, TypeError, ValueError):
raise BTFailure('Not a valid bencoded string')
else:
return r
r, l = decode_func[x[0]](x, 0)
except (IndexError, KeyError, ValueError):
raise Exception("not a valid bencoded string")
return r
from types import StringType, IntType, LongType, DictType, ListType, TupleType
class Bencached:
class Bencached(object):
__slots__ = ['bencoded']
def __init__(self, s):
self.bencoded = s
def encode_bencached(x, r):
def encode_bencached(x,r):
r.append(x.bencoded)
def encode_int(x, r):
r.extend((INT_DELIM, str(x).encode('utf8'), END_DELIM))
r.extend(('i', str(x), 'e'))
def encode_bool(x, r):
encode_int(1 if x else 0, r)
if x:
encode_int(1, r)
else:
encode_int(0, r)
def encode_string(x, r):
encode_bytes(x.encode('utf8'), r)
def encode_bytes(x, r):
r.extend((str(len(x)).encode('utf8'), BYTE_SEP, x))
r.extend((str(len(x)), ':', x))
def encode_list(x, r):
r.append(LIST_DELIM)
r.append('l')
for i in x:
encode_func[type(i)](i, r)
r.append(END_DELIM)
r.append('e')
def encode_dict(x, r):
r.append(DICT_DELIM)
for k, v in sorted(x.items()):
try:
k = k.encode('utf8')
except AttributeError:
pass
r.extend((str(len(k)).encode('utf8'), BYTE_SEP, k))
def encode_dict(x,r):
r.append('d')
ilist = x.items()
ilist.sort()
for k, v in ilist:
r.extend((str(len(k)), ':', k))
encode_func[type(v)](v, r)
r.append(END_DELIM)
r.append('e')
encode_func = {}
encode_func[Bencached] = encode_bencached
encode_func[int] = encode_int
encode_func[list] = encode_list
encode_func[tuple] = encode_list
encode_func[dict] = encode_dict
encode_func[bool] = encode_bool
encode_func[str] = encode_string
encode_func[bytes] = encode_bytes
encode_func[IntType] = encode_int
encode_func[LongType] = encode_int
encode_func[StringType] = encode_string
encode_func[ListType] = encode_list
encode_func[TupleType] = encode_list
encode_func[DictType] = encode_dict
try:
from types import BooleanType
encode_func[BooleanType] = encode_bool
except ImportError:
pass
def bencode(x):
r = []
encode_func[type(x)](x, r)
return b''.join(r)
return ''.join(r)

File diff suppressed because it is too large Load Diff

View File

@ -1,486 +1,221 @@
#
# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com>
# component.py
#
# 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.
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import logging
import traceback
from collections import defaultdict
from twisted.internet import reactor
from twisted.internet.defer import DeferredList, fail, maybeDeferred, succeed
from twisted.internet.task import LoopingCall, deferLater
log = logging.getLogger(__name__)
class ComponentAlreadyRegistered(Exception):
pass
class ComponentException(Exception):
def __init__(self, message, tb):
super().__init__(message)
self.message = message
self.tb = tb
def __str__(self):
s = super().__str__()
return '{}\n{}'.format(s, ''.join(self.tb))
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.message == other.message
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
import gobject
from deluge.log import LOG as log
COMPONENT_STATE = [
"Stopped",
"Started",
"Paused"
]
class Component:
"""Component objects are singletons managed by the :class:`ComponentRegistry`.
When a new Component object is instantiated, it will be automatically
registered with the :class:`ComponentRegistry`.
The ComponentRegistry has the ability to start, stop, pause and shutdown the
components registered with it.
**Events:**
**start()** - This method is called when the client has connected to a
Deluge core.
**stop()** - This method is called when the client has disconnected from a
Deluge core.
**update()** - This method is called every 1 second by default while the
Componented is in a *Started* state. The interval can be
specified during instantiation. The update() timer can be
paused by instructing the :class:`ComponentRegistry` to pause
this Component.
**shutdown()** - This method is called when the client is exiting. If the
Component is in a "Started" state when this is called, a
call to stop() will be issued prior to shutdown().
**States:**
A Component can be in one of these 5 states.
**Started** - The Component has been started by the :class:`ComponentRegistry`
and will have it's update timer started.
**Starting** - The Component has had it's start method called, but it hasn't
fully started yet.
**Stopped** - The Component has either been stopped or has yet to be started.
**Stopping** - The Component has had it's stop method called, but it hasn't
fully stopped yet.
**Paused** - The Component has had it's update timer stopped, but will
still be considered in a Started state.
"""
def __init__(self, name, interval=1, depend=None):
"""Initialize component.
Args:
name (str): Name of component.
interval (int, optional): The interval in seconds to call the update function.
depend (list, optional): The names of components this component depends on.
"""
self._component_name = name
self._component_interval = interval
self._component_depend = depend
self._component_state = 'Stopped'
self._component_timer = None
self._component_starting_deferred = None
self._component_stopping_deferred = None
_ComponentRegistry.register(self)
def __del__(self):
if _ComponentRegistry:
_ComponentRegistry.deregister(self)
def _component_start_timer(self):
if hasattr(self, 'update'):
self._component_timer = LoopingCall(self.update)
self._component_timer.start(self._component_interval)
def _component_start(self):
def on_start(result):
self._component_state = 'Started'
self._component_starting_deferred = None
self._component_start_timer()
return True
def on_start_fail(result):
self._component_state = 'Stopped'
self._component_starting_deferred = None
log.error(result)
return fail(result)
if self._component_state == 'Stopped':
if hasattr(self, 'start'):
self._component_state = 'Starting'
d = deferLater(reactor, 0, self.start)
d.addCallbacks(on_start, on_start_fail)
self._component_starting_deferred = d
else:
d = maybeDeferred(on_start, None)
elif self._component_state == 'Starting':
return self._component_starting_deferred
elif self._component_state == 'Started':
d = succeed(True)
else:
d = fail(
ComponentException(
'Trying to start component "%s" but it is '
'not in a stopped state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_stop(self):
def on_stop(result):
self._component_state = 'Stopped'
if self._component_timer and self._component_timer.running:
self._component_timer.stop()
return True
def on_stop_fail(result):
self._component_state = 'Started'
self._component_stopping_deferred = None
log.error(result)
return result
if self._component_state != 'Stopped' and self._component_state != 'Stopping':
if hasattr(self, 'stop'):
self._component_state = 'Stopping'
d = maybeDeferred(self.stop)
d.addCallback(on_stop)
d.addErrback(on_stop_fail)
self._component_stopping_deferred = d
else:
d = maybeDeferred(on_stop, None)
if self._component_state == 'Stopping':
return self._component_stopping_deferred
return succeed(None)
def _component_pause(self):
def on_pause(result):
self._component_state = 'Paused'
if self._component_state == 'Started':
if self._component_timer and self._component_timer.running:
d = maybeDeferred(self._component_timer.stop)
d.addCallback(on_pause)
else:
d = succeed(None)
elif self._component_state == 'Paused':
d = succeed(None)
else:
d = fail(
ComponentException(
'Trying to pause component "%s" but it is '
'not in a started state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_resume(self):
def on_resume(result):
self._component_state = 'Started'
if self._component_state == 'Paused':
d = maybeDeferred(self._component_start_timer)
d.addCallback(on_resume)
else:
d = fail(
ComponentException(
'Trying to resume component "%s" but it is '
'not in a paused state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_shutdown(self):
def on_stop(result):
if hasattr(self, 'shutdown'):
return maybeDeferred(self.shutdown)
return succeed(None)
d = self._component_stop()
d.addCallback(on_stop)
return d
def __init__(self, name, interval=1000, depend=None):
# Register with the ComponentRegistry
register(name, self, depend)
self._interval = interval
self._timer = None
self._state = COMPONENT_STATE.index("Stopped")
def get_state(self):
return self._component_state
return self._state
def start(self):
pass
def _start(self):
self._state = COMPONENT_STATE.index("Started")
if self._update():
self._timer = gobject.timeout_add(self._interval, self._update)
def stop(self):
pass
def update(self):
pass
def _stop(self):
self._state = COMPONENT_STATE.index("Stopped")
try:
gobject.source_remove(self._timer)
except:
pass
def _pause(self):
self._state = COMPONENT_STATE.index("Paused")
try:
gobject.source_remove(self._timer)
except:
pass
def _resume(self):
self._start()
def shutdown(self):
pass
def _update(self):
try:
self.update()
except AttributeError:
# This will stop the timer since the component doesn't have an
# update method.
return False
return True
class ComponentRegistry:
"""The ComponentRegistry holds a list of currently registered :class:`Component` objects.
It is used to manage the Components by starting, stopping, pausing and shutting them down.
"""
def __init__(self):
self.components = {}
# Stores all of the components that are dependent on a particular component
self.dependents = defaultdict(list)
self.depend = {}
def register(self, obj):
"""Register a component object with the registry.
def register(self, name, obj, depend):
"""Registers a component.. depend must be list or None"""
log.debug("Registered %s with ComponentRegistry..", name)
self.components[name] = obj
if depend != None:
self.depend[name] = depend
Note:
This is done automatically when a Component object is instantiated.
def get(self, name):
"""Returns a reference to the component 'name'"""
return self.components[name]
Args:
obj (Component): A component object to register.
def start(self):
"""Starts all components"""
for component in self.components.keys():
self.start_component(component)
Raises:
ComponentAlreadyRegistered: If a component with the same name is already registered.
def start_component(self, name):
"""Starts a component"""
# Check to see if this component has any dependencies
if self.depend.has_key(name):
for depend in self.depend[name]:
self.start_component(depend)
# Only start if the component is stopped.
if self.components[name].get_state() == \
COMPONENT_STATE.index("Stopped"):
log.debug("Starting component %s..", name)
self.components[name].start()
self.components[name]._start()
"""
name = obj._component_name
if name in self.components:
raise ComponentAlreadyRegistered(
'Component already registered with name %s' % name
)
def stop(self):
"""Stops all components"""
for component in self.components.keys():
self.stop_component(component)
self.components[obj._component_name] = obj
if obj._component_depend:
for depend in obj._component_depend:
self.dependents[depend].append(name)
def stop_component(self, component):
if self.components[component].get_state() != \
COMPONENT_STATE.index("Stopped"):
log.debug("Stopping component %s..", component)
self.components[component].stop()
self.components[component]._stop()
def deregister(self, obj):
"""Deregister a component from the registry. A stop will be
issued to the component prior to deregistering it.
def pause(self):
"""Pauses all components. Stops calling update()"""
for component in self.components.keys():
self.pause_component(component)
Args:
obj (Component): a component object to deregister
def pause_component(self, component):
if self.components[component].get_state() not in \
[COMPONENT_STATE.index("Paused"), COMPONENT_STATE.index("Stopped")]:
log.debug("Pausing component %s..", component)
self.components[component]._pause()
Returns:
Deferred: a deferred object that will fire once the Component has been
successfully deregistered
def resume(self):
"""Resumes all components. Starts calling update()"""
for component in self.components.keys():
self.resume_component(component)
"""
if obj in self.components.values():
log.debug('Deregistering Component: %s', obj._component_name)
d = self.stop([obj._component_name])
def on_stop(result, name):
# Component may have been removed, so pop to ensure it doesn't fail
self.components.pop(name, None)
return d.addCallback(on_stop, obj._component_name)
else:
return succeed(None)
def start(self, names=None):
"""Start Components, and their dependencies, that are currently in a Stopped state.
Note:
If no names are specified then all registered components will be started.
Args:
names (list): A list of Components to start and their dependencies.
Returns:
Deferred: Fired once all Components have been successfully started.
"""
# Start all the components if names is empty
if not names:
names = list(self.components)
elif isinstance(names, str):
names = [names]
def on_depends_started(result, name):
return self.components[name]._component_start()
deferreds = []
for name in names:
if self.components[name]._component_depend:
# This component has depends, so we need to start them first.
d = self.start(self.components[name]._component_depend)
d.addCallback(on_depends_started, name)
deferreds.append(d)
else:
deferreds.append(self.components[name]._component_start())
return DeferredList(deferreds)
def stop(self, names=None):
"""Stop Components that are currently not in a Stopped state.
Note:
If no names are specified then all registered components will be stopped.
Args:
names (list): A list of Components to stop.
Returns:
Deferred: Fired once all Components have been successfully stopped.
"""
if not names:
names = list(self.components)
elif isinstance(names, str):
names = [names]
def on_dependents_stopped(result, name):
return self.components[name]._component_stop()
stopped_in_deferred = set()
deferreds = []
for name in names:
if name in stopped_in_deferred:
continue
if name in self.components:
if name in self.dependents:
# If other components depend on this component, stop them first
d = self.stop(self.dependents[name]).addCallback(
on_dependents_stopped, name
)
deferreds.append(d)
stopped_in_deferred.update(self.dependents[name])
else:
deferreds.append(self.components[name]._component_stop())
return DeferredList(deferreds)
def pause(self, names=None):
"""Pause Components that are currently in a Started state.
Note:
If no names are specified then all registered components will be paused.
Args:
names (list): A list of Components to pause.
Returns:
Deferred: Fired once all Components have been successfully paused.
"""
if not names:
names = list(self.components)
elif isinstance(names, str):
names = [names]
deferreds = []
for name in names:
if self.components[name]._component_state == 'Started':
deferreds.append(self.components[name]._component_pause())
return DeferredList(deferreds)
def resume(self, names=None):
"""Resume Components that are currently in a Paused state.
Note:
If no names are specified then all registered components will be resumed.
Args:
names (list): A list of Components to to resume.
Returns:
Deferred: Fired once all Components have been successfully resumed.
"""
if not names:
names = list(self.components)
elif isinstance(names, str):
names = [names]
deferreds = []
for name in names:
if self.components[name]._component_state == 'Paused':
deferreds.append(self.components[name]._component_resume())
return DeferredList(deferreds)
def shutdown(self):
"""Shutdown all Components regardless of state.
This will call stop() on all the components prior to shutting down. This should be called
when the program is exiting to ensure all Components have a chance to properly shutdown.
Returns:
Deferred: Fired once all Components have been successfully shut down.
"""
def on_stopped(result):
return DeferredList(
[comp._component_shutdown() for comp in list(self.components.values())]
)
return self.stop(list(self.components)).addCallback(on_stopped)
def resume_component(self, component):
if self.components[component].get_state() == COMPONENT_STATE.index("Paused"):
log.debug("Resuming component %s..", component)
self.components[component]._resume()
def update(self):
"""Update all Components that are in a Started state."""
for component in self.components.items():
"""Updates all components"""
for component in self.components.keys():
# Only update the component if it's started
if self.components[component].get_state() == \
COMPONENT_STATE.index("Started"):
self.components[component].update()
return True
def shutdown(self):
"""Shuts down all components. This should be called when the program
exits so that components can do any necessary clean-up."""
# Stop all components first
self.stop()
for component in self.components.keys():
log.debug("Shutting down component %s..", component)
try:
component.update()
except BaseException as ex:
log.exception(ex)
self.components[component].shutdown()
except Exception, e:
log.debug("Unable to call shutdown(): %s", e)
_ComponentRegistry = ComponentRegistry()
deregister = _ComponentRegistry.deregister
start = _ComponentRegistry.start
stop = _ComponentRegistry.stop
pause = _ComponentRegistry.pause
resume = _ComponentRegistry.resume
update = _ComponentRegistry.update
shutdown = _ComponentRegistry.shutdown
def register(name, obj, depend=None):
"""Registers a component with the registry"""
_ComponentRegistry.register(name, obj, depend)
def start(component=None):
"""Starts all components"""
if component == None:
_ComponentRegistry.start()
else:
_ComponentRegistry.start_component(component)
def get(name):
"""Return a reference to a component.
def stop(component=None):
"""Stops all or specified components"""
if component == None:
_ComponentRegistry.stop()
else:
_ComponentRegistry.stop_component(component)
Args:
name (str): The Component name to get.
def pause(component=None):
"""Pauses all or specificed components"""
if component == None:
_ComponentRegistry.pause()
else:
_ComponentRegistry.pause_component(component)
Returns:
Component: The Component object.
def resume(component=None):
"""Resumes all or specificed components"""
if component == None:
_ComponentRegistry.resume()
else:
_ComponentRegistry.resume_component(component)
Raises:
KeyError: If the Component does not exist.
def update():
"""Updates all components"""
_ComponentRegistry.update()
"""
return _ComponentRegistry.components[name]
def shutdown():
"""Shutdowns all components"""
_ComponentRegistry.shutdown()
def get(component):
"""Return a reference to the component"""
return _ComponentRegistry.get(component)

View File

@ -1,459 +1,253 @@
#
# config.py
#
# Copyright (C) 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.
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
"""
Deluge Config Module
This module is used for loading and saving of configuration files.. or anything
really.
The format of the config file is two json encoded dicts:
<version dict>
<content dict>
The version dict contains two keys: file and format. The format version is
controlled by the Config class. It should only be changed when anything below
it is changed directly by the Config class. An example of this would be if we
changed the serializer for the content to something different.
The config file version is changed by the 'owner' of the config file. This is
to signify that there is a change in the naming of some config keys or something
similar along those lines.
The content is simply the dict to be saved and will be serialized before being
written.
Converting
Since the format of the config could change, there needs to be a way to have
the Config object convert to newer formats. To do this, you will need to
register conversion functions for various versions of the config file. Note that
this can only be done for the 'config file version' and not for the 'format'
version as this will be done internally.
"""
import json
import logging
import os
import pickle
import cPickle as pickle
import shutil
from codecs import getwriter
from tempfile import NamedTemporaryFile
import os
import gobject
import deluge.common
from deluge.log import LOG as log
from deluge.common import JSON_FORMAT, get_default_config_dir
def prop(func):
"""Function decorator for defining property attributes
log = logging.getLogger(__name__)
The decorated function is expected to return a dictionary
containing one or more of the following pairs:
fget - function for getting attribute value
fset - function for setting attribute value
fdel - function for deleting attribute
This can be conveniently constructed by the locals() builtin
function; see:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
"""
return property(doc=func.__doc__, **func())
class Config(object):
"""
This class is used to access/create/modify config files
def find_json_objects(text, decoder=json.JSONDecoder()):
"""Find json objects in text.
Args:
text (str): The text to find json objects within.
Returns:
list: A list of tuples containing start and end locations of json
objects in the text. e.g. [(start, end), ...]
:param filename: the name of the config file
:param defaults: dictionary of default values
:param config_dir: the path to the config directory
"""
objects = []
offset = 0
while True:
try:
start = text.index('{', offset)
except ValueError:
break
try:
__, index = decoder.raw_decode(text[start:])
except json.decoder.JSONDecodeError:
offset = start + 1
else:
offset = start + index
objects.append((start, offset))
return objects
def cast_to_existing_type(value, old_value):
"""Attempt to convert new value type to match old value type"""
types_match = isinstance(old_value, (type(None), type(value)))
if value is not None and not types_match:
old_type = type(old_value)
# Skip convert to bytes since requires knowledge of encoding and value should
# be unicode anyway.
if old_type is bytes:
return value
return old_type(value)
return value
class Config:
"""This class is used to access/create/modify config files.
Args:
filename (str): The config filename.
defaults (dict): The default config values to insert before loading the config file.
config_dir (str): the path to the config directory.
file_version (int): The file format for the default config values when creating
a fresh config. This value should be increased whenever a new migration function is
setup to convert old config files. (default: 1)
log_mask_funcs (dict): A dict of key:function, used to mask sensitive
key values (e.g. passwords) when logging is enabled.
"""
def __init__(
self,
filename,
defaults=None,
config_dir=None,
file_version=1,
log_mask_funcs=None,
):
def __init__(self, filename, defaults=None, config_dir=None):
self.__config = {}
self.__previous_config = {}
self.__set_functions = {}
self.__change_callbacks = []
self.__log_mask_funcs = log_mask_funcs if log_mask_funcs else {}
# These hold the version numbers and they will be set when loaded
self.__version = {'format': 1, 'file': file_version}
# This will get set with a reactor.callLater whenever a config option
self.__change_callback = None
# This will get set with a gobject.timeout_add whenever a config option
# is set.
self._save_timer = None
self.__save_timer = None
if defaults:
for key, value in defaults.items():
self.set_item(key, value, default=True)
self.__config = defaults
# Load the config from file in the config_dir
if config_dir:
self.__config_file = os.path.join(config_dir, filename)
else:
self.__config_file = get_default_config_dir(filename)
self.__config_file = deluge.common.get_default_config_dir(filename)
self.load()
def callLater(self, period, func, *args, **kwargs): # noqa: N802 ignore camelCase
"""Wrapper around reactor.callLater for test purpose."""
from twisted.internet import reactor
return reactor.callLater(period, func, *args, **kwargs)
def __contains__(self, item):
return item in self.__config
def __setitem__(self, key, value):
"""See set_item"""
"""
See
:meth:`set_item`
"""
return self.set_item(key, value)
def set_item(self, key, value, default=False):
"""Sets item 'key' to 'value' in the config dictionary.
def set_item(self, key, value):
"""
Sets item 'key' to 'value' in the config dictionary, but does not allow
changing the item's type unless it is None
Does not allow changing the item's type unless it is None.
:param key: string, item to change to change
:param value: the value to change item to, must be same type as what is currently in the config
If the types do not match, it will attempt to convert it to the
set type before raising a ValueError.
:raises ValueError: raised when the type of value is not the same as what is currently in the config
Args:
key (str): Item to change to change.
value (any): The value to change item to, must be same type as what is
currently in the config.
default (optional, bool): When setting a default value skip func or save
callbacks.
**Usage**
Raises:
ValueError: Raised when the type of value is not the same as what is
currently in the config and it could not convert the value.
Examples:
>>> config = Config('test.conf')
>>> config['test'] = 5
>>> config['test']
5
>>> config = Config("test.conf")
>>> config["test"] = 5
>>> config["test"]
5
"""
if isinstance(value, bytes):
value = value.decode()
if key in self.__config:
try:
value = cast_to_existing_type(value, self.__config[key])
except ValueError:
log.warning('Value Type "%s" invalid for key: %s', type(value), key)
raise
else:
if self.__config[key] == value:
return
if log.isEnabledFor(logging.DEBUG):
if key in self.__log_mask_funcs:
value = self.__log_mask_funcs[key](value)
log.debug(
'Setting key "%s" to: %s (of type: %s)',
key,
value,
type(value),
)
self.__config[key] = value
# Skip save or func callbacks if setting default value for keys
if default:
if not self.__config.has_key(key):
self.__config[key] = value
log.debug("Setting '%s' to %s of %s", key, value, type(value))
return
if self.__config[key] == value:
return
# Do not allow the type to change unless it is None
oldtype, newtype = type(self.__config[key]), type(value)
if value is not None and oldtype != type(None) and oldtype != newtype:
try:
value = oldtype(value)
except ValueError:
log.warning("Type '%s' invalid for '%s'", newtype, key)
raise
log.debug("Setting '%s' to %s of %s", key, value, type(value))
# Make a copy of the current config prior to changing it
self.__previous_config.update(self.__config)
self.__config[key] = value
# Run the set_function for this key if any
for func in self.__set_functions.get(key, []):
self.callLater(0, func, key, value)
try:
def do_change_callbacks(key, value):
for func in self.__change_callbacks:
func(key, value)
self.callLater(0, do_change_callbacks, key, value)
except Exception:
gobject.idle_add(self.__set_functions[key], key, value)
except KeyError:
pass
try:
gobject.idle_add(self.__change_callback, key, value)
except:
pass
# We set the save_timer for 5 seconds if not already set
if not self._save_timer or not self._save_timer.active():
self._save_timer = self.callLater(5, self.save)
if not self.__save_timer:
self.__save_timer = gobject.timeout_add(5000, self.save)
def __getitem__(self, key):
"""See get_item"""
"""
See
:meth:`get_item`
"""
return self.get_item(key)
def get_item(self, key):
"""Gets the value of item 'key'.
"""
Gets the value of item 'key'
Args:
key (str): The item for which you want it's value.
:param key: the item for which you want it's value
:return: the value of item 'key'
Returns:
any: The value of item 'key'.
:raises KeyError: if 'key' is not in the config dictionary
Raises:
ValueError: If 'key' is not in the config dictionary.
**Usage**
Examples:
>>> config = Config('test.conf', defaults={'test': 5})
>>> config['test']
5
>>> config = Config("test.conf", defaults={"test": 5})
>>> config["test"]
5
"""
return self.__config[key]
def get(self, key, default=None):
"""Gets the value of item 'key' if key is in the config, else default.
If default is not given, it defaults to None, so that this method
never raises a KeyError.
Args:
key (str): the item for which you want it's value
default (any): the default value if key is missing
Returns:
any: The value of item 'key' or default.
Examples:
>>> config = Config('test.conf', defaults={'test': 5})
>>> config.get('test', 10)
5
>>> config.get('bad_key', 10)
10
"""
try:
return self.get_item(key)
except KeyError:
return default
def __delitem__(self, key):
"""
See
:meth:`del_item`
"""
self.del_item(key)
def del_item(self, key):
"""Deletes item with a specific key from the configuration.
Args:
key (str): The item which you wish to delete.
Raises:
ValueError: If 'key' is not in the config dictionary.
Examples:
>>> config = Config('test.conf', defaults={'test': 5})
>>> del config['test']
"""
del self.__config[key]
# We set the save_timer for 5 seconds if not already set
if not self._save_timer or not self._save_timer.active():
self._save_timer = self.callLater(5, self.save)
def register_change_callback(self, callback):
"""Registers a callback function for any changed value.
"""
Registers a callback function that will be called when a value is changed in the config dictionary
Will be called when any value is changed in the config dictionary.
:param callback: the function, callback(key, value)
Args:
callback (func): The function to call with parameters: f(key, value).
**Usage**
Examples:
>>> config = Config('test.conf', defaults={'test': 5})
>>> def cb(key, value):
... print key, value
...
>>> config.register_change_callback(cb)
>>> config = Config("test.conf", defaults={"test": 5})
>>> def cb(key, value):
... print key, value
...
>>> config.register_change_callback(cb)
"""
self.__change_callbacks.append(callback)
self.__change_callback = callback
def register_set_function(self, key, function, apply_now=True):
"""Register a function to be called when a config value changes.
"""
Register a function to be called when a config value changes
Args:
key (str): The item to monitor for change.
function (func): The function to call when the value changes, f(key, value).
apply_now (bool): If True, the function will be called immediately after it's registered.
:param key: the item to monitor for change
:param function: the function to call when the value changes, f(key, value)
:keyword apply_now: if True, the function will be called after it's registered
Examples:
>>> config = Config('test.conf', defaults={'test': 5})
>>> def cb(key, value):
... print key, value
...
>>> config.register_set_function('test', cb, apply_now=True)
test 5
**Usage**
>>> config = Config("test.conf", defaults={"test": 5})
>>> def cb(key, value):
... print key, value
...
>>> config.register_set_function("test", cb, apply_now=True)
test 5
"""
log.debug('Registering function for %s key..', key)
if key not in self.__set_functions:
self.__set_functions[key] = []
self.__set_functions[key].append(function)
log.debug("Registering function for %s key..", key)
self.__set_functions[key] = function
# Run the function now if apply_now is set
if apply_now:
function(key, self.__config[key])
self.__set_functions[key](key, self.__config[key])
return
def apply_all(self):
"""Calls all set functions.
"""
Calls all set functions
Examples:
>>> config = Config('test.conf', defaults={'test': 5})
>>> def cb(key, value):
... print key, value
...
>>> config.register_set_function('test', cb, apply_now=False)
>>> config.apply_all()
test 5
**Usage**
>>> config = Config("test.conf", defaults={"test": 5})
>>> def cb(key, value):
... print key, value
...
>>> config.register_set_function("test", cb, apply_now=False)
>>> config.apply_all()
test 5
"""
log.debug('Calling all set functions..')
for key, value in self.__set_functions.items():
for func in value:
func(key, self.__config[key])
def apply_set_functions(self, key):
"""Calls set functions for `:param:key`.
Args:
key (str): the config key
"""
log.debug('Calling set functions for key %s..', key)
if key in self.__set_functions:
for func in self.__set_functions[key]:
func(key, self.__config[key])
log.debug("Calling all set functions..")
for key, value in self.__set_functions.iteritems():
value(key, self.__config[key])
def load(self, filename=None):
"""Load a config file.
"""
Load a config file
:param filename: if None, uses filename set in object initialization
Args:
filename (str): If None, uses filename set in object initialization
"""
if not filename:
filename = self.__config_file
try:
with open(filename, encoding='utf8') as _file:
data = _file.read()
except OSError as ex:
log.warning('Unable to open config file %s: %s', filename, ex)
return
self.__config.update(pickle.load(open(filename, "rb")))
except Exception, e:
log.warning("Unable to load config file: %s", filename)
objects = find_json_objects(data)
if not len(objects):
# No json objects found, try depickling it
try:
self.__config.update(pickle.loads(data))
except Exception as ex:
log.exception(ex)
log.warning('Unable to load config file: %s', filename)
elif len(objects) == 1:
start, end = objects[0]
try:
self.__config.update(json.loads(data[start:end]))
except Exception as ex:
log.exception(ex)
log.warning('Unable to load config file: %s', filename)
elif len(objects) == 2:
try:
start, end = objects[0]
self.__version.update(json.loads(data[start:end]))
start, end = objects[1]
self.__config.update(json.loads(data[start:end]))
except Exception as ex:
log.exception(ex)
log.warning('Unable to load config file: %s', filename)
if not log.isEnabledFor(logging.DEBUG):
return
config = self.__config
if self.__log_mask_funcs:
config = {
key: self.__log_mask_funcs[key](config[key])
if key in self.__log_mask_funcs
else config[key]
for key in config
}
log.debug(
'Config %s version: %s.%s loaded: %s',
filename,
self.__version['format'],
self.__version['file'],
config,
)
log.debug("Config %s loaded: %s", filename, self.__config)
def save(self, filename=None):
"""Save configuration to disk.
"""
Save configuration to disk
Args:
filename (str): If None, uses filename set in object initialization
Returns:
bool: Whether or not the save succeeded.
:param filename: if None, uses filename set in object initiliazation
"""
if not filename:
@ -461,108 +255,36 @@ class Config:
# Check to see if the current config differs from the one on disk
# We will only write a new config file if there is a difference
try:
with open(filename, encoding='utf8') as _file:
data = _file.read()
objects = find_json_objects(data)
start, end = objects[0]
version = json.loads(data[start:end])
start, end = objects[1]
loaded_data = json.loads(data[start:end])
if self.__config == loaded_data and self.__version == version:
if self.__config == pickle.load(open(filename, "rb")):
# The config has not changed so lets just return
if self._save_timer and self._save_timer.active():
self._save_timer.cancel()
return True
except (OSError, IndexError) as ex:
log.warning('Unable to open config file: %s because: %s', filename, ex)
self.__save_timer = None
return
except Exception, e:
log.warning("Unable to open config file: %s", filename)
self.__save_timer = None
# Save the new config and make sure it's written to disk
try:
with NamedTemporaryFile(
prefix=os.path.basename(filename) + '.', delete=False
) as _file:
filename_tmp = _file.name
log.debug('Saving new config file %s', filename_tmp)
json.dump(self.__version, getwriter('utf8')(_file), **JSON_FORMAT)
json.dump(self.__config, getwriter('utf8')(_file), **JSON_FORMAT)
_file.flush()
os.fsync(_file.fileno())
except OSError as ex:
log.error('Error writing new config file: %s', ex)
return False
# Resolve symlinked config files before backing up and saving.
filename = os.path.realpath(filename)
# Make a backup of the old config
try:
log.debug('Backing up old config file to %s.bak', filename)
shutil.move(filename, filename + '.bak')
except OSError as ex:
log.warning('Unable to backup old config: %s', ex)
log.debug("Saving new config file %s", filename + ".new")
pickle.dump(self.__config, open(filename + ".new", "wb"))
except Exception, e:
log.error("Error writing new config file: %s", e)
return
# The new config file has been written successfully, so let's move it over
# the existing one.
try:
log.debug('Moving new config file %s to %s', filename_tmp, filename)
shutil.move(filename_tmp, filename)
except OSError as ex:
log.error('Error moving new config file: %s', ex)
return False
else:
return True
finally:
if self._save_timer and self._save_timer.active():
self._save_timer.cancel()
def run_converter(self, input_range, output_version, func):
"""Runs a function that will convert file versions.
Args:
input_range (tuple): (int, int) The range of input versions this function will accept.
output_version (int): The version this function will convert to.
func (func): The function that will do the conversion, it will take the config
dict as an argument and return the augmented dict.
Raises:
ValueError: If output_version is less than the input_range.
"""
if output_version in input_range or output_version <= max(input_range):
raise ValueError('output_version needs to be greater than input_range')
if self.__version['file'] not in input_range:
log.debug(
'File version %s is not in input_range %s, ignoring converter function..',
self.__version['file'],
input_range,
)
log.debug("Moving new config file %s to %s..", filename + ".new", filename)
shutil.move(filename + ".new", filename)
except Exception, e:
log.error("Error moving new config file: %s", e)
return
try:
self.__config = func(self.__config)
except Exception as ex:
log.exception(ex)
log.error(
'There was an exception try to convert config file %s %s to %s',
self.__config_file,
self.__version['file'],
output_version,
)
raise ex
else:
self.__version['file'] = output_version
self.save()
@property
def config_file(self):
return self.__config_file
@property
def config(self):
@prop
def config():
"""The config dictionary"""
return self.__config
@config.deleter
def config(self):
return self.save()
def fget(self):
return self.__config
def fdel(self):
return self.save()
return locals()

View File

@ -1,73 +1,61 @@
#
# configmanager.py
#
# 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.
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import logging
import gobject
import os
import os.path
import deluge.common
import deluge.log
from deluge.log import LOG as log
from deluge.config import Config
log = logging.getLogger(__name__)
class _ConfigManager:
def __init__(self):
log.debug('ConfigManager started..')
log.debug("ConfigManager started..")
self.config_files = {}
self.__config_directory = None
@property
def config_directory(self):
if self.__config_directory is None:
self.__config_directory = deluge.common.get_default_config_dir()
return self.__config_directory
self.config_directory = deluge.common.get_default_config_dir()
# Set a 5 minute timer to call save()
gobject.timeout_add(300000, self.save)
def __del__(self):
log.debug("ConfigManager stopping..")
del self.config_files
def set_config_dir(self, directory):
"""
Sets the config directory.
:param directory: str, the directory where the config info should be
:returns bool: True if successfully changed directory, False if not
"""
if not directory:
return False
# Ensure absolute dirpath
directory = os.path.abspath(directory)
log.info('Setting config directory to: %s', directory)
"""Sets the config directory"""
if directory == None:
return
log.info("Setting config directory to: %s", directory)
if not os.path.exists(directory):
# Try to create the config folder if it doesn't exist
try:
os.makedirs(directory)
except OSError as ex:
log.error('Unable to make config directory: %s', ex)
return False
elif not os.path.isdir(directory):
log.error('Config directory needs to be a directory!')
return False
except Exception, e:
log.warning("Unable to make config directory: %s", e)
self.__config_directory = directory
# Reset the config_files so we don't get config from old config folder
# XXX: Probably should have it go through the config_files dict and try
# to reload based on the new config directory
self.save()
self.config_files = {}
deluge.log.tweak_logging_levels()
return True
self.config_directory = directory
def get_config_dir(self):
return self.config_directory
@ -81,47 +69,35 @@ class _ConfigManager:
def save(self):
"""Saves all the configs to disk."""
for value in self.config_files.values():
value.save()
for key in self.config_files.keys():
self.config_files[key].save()
# We need to return True to keep the timer active
return True
def get_config(self, config_file, defaults=None, file_version=1):
def get_config(self, config_file, defaults=None):
"""Get a reference to the Config object for this filename"""
log.debug('Getting config: %s', config_file)
log.debug("Getting config '%s'", config_file)
# Create the config object if not already created
if config_file not in self.config_files:
self.config_files[config_file] = Config(
config_file,
defaults,
config_dir=self.config_directory,
file_version=file_version,
)
if config_file not in self.config_files.keys():
self.config_files[config_file] = Config(config_file, defaults, self.config_directory)
return self.config_files[config_file]
# Singleton functions
_configmanager = _ConfigManager()
def ConfigManager(config, defaults=None, file_version=1): # NOQA: N802
return _configmanager.get_config(
config, defaults=defaults, file_version=file_version
)
def ConfigManager(config, defaults=None):
return _configmanager.get_config(config, defaults)
def set_config_dir(directory):
"""Sets the config directory, else just uses default"""
return _configmanager.set_config_dir(deluge.common.decode_bytes(directory))
return _configmanager.set_config_dir(directory)
def get_config_dir(filename=None):
if filename is not None:
if filename != None:
return os.path.join(_configmanager.get_config_dir(), filename)
else:
return _configmanager.get_config_dir()
def close(config):
return _configmanager.close(config)

View File

@ -1,192 +0,0 @@
#
# 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 tempfile
import warnings
from unittest.mock import Mock, patch
import pytest
import pytest_twisted
from twisted.internet import reactor
from twisted.internet.defer import Deferred, maybeDeferred
from twisted.internet.error import CannotListenError
from twisted.python.failure import Failure
import deluge.component as _component
import deluge.configmanager
from deluge.common import get_localhost_auth
from deluge.tests import common
from deluge.ui.client import client as _client
DEFAULT_LISTEN_PORT = 58900
@pytest.fixture
def listen_port(request):
if request and 'daemon' in request.fixturenames:
try:
return request.getfixturevalue('daemon').listen_port
except Exception:
pass
return DEFAULT_LISTEN_PORT
@pytest.fixture
def mock_callback():
"""Returns a `Mock` object which can be registered as a callback to test against.
If callback was not called within `timeout` seconds, it will raise a TimeoutError.
The returned Mock instance will have a `deferred` attribute which will complete when the callback has been called.
"""
def reset():
if mock.called:
original_reset_mock()
deferred = Deferred()
deferred.addTimeout(0.5, reactor)
mock.side_effect = lambda *args, **kw: deferred.callback((args, kw))
mock.deferred = deferred
mock = Mock()
original_reset_mock = mock.reset_mock
mock.reset_mock = reset
mock.reset_mock()
return mock
@pytest.fixture
def config_dir(tmp_path):
deluge.configmanager.set_config_dir(tmp_path)
yield tmp_path
@pytest_twisted.async_yield_fixture()
async def client(request, config_dir, monkeypatch, listen_port):
# monkeypatch.setattr(
# _client, 'connect', functools.partial(_client.connect, port=listen_port)
# )
try:
username, password = get_localhost_auth()
except Exception:
username, password = '', ''
await _client.connect(
'localhost',
port=listen_port,
username=username,
password=password,
)
yield _client
if _client.connected():
await _client.disconnect()
@pytest_twisted.async_yield_fixture
async def daemon(request, config_dir):
listen_port = DEFAULT_LISTEN_PORT
logfile = f'daemon_{request.node.name}.log'
if hasattr(request.cls, 'daemon_custom_script'):
custom_script = request.cls.daemon_custom_script
else:
custom_script = ''
for dummy in range(10):
try:
d, daemon = common.start_core(
listen_port=listen_port,
logfile=logfile,
timeout=5,
timeout_msg='Timeout!',
custom_script=custom_script,
print_stdout=True,
print_stderr=True,
config_directory=config_dir,
)
await d
except CannotListenError as ex:
exception_error = ex
listen_port += 1
except (KeyboardInterrupt, SystemExit):
raise
else:
break
else:
raise exception_error
daemon.listen_port = listen_port
yield daemon
await daemon.kill()
@pytest.fixture(autouse=True)
def common_fixture(config_dir, request, monkeypatch, listen_port):
"""Adds some instance attributes to test classes for backwards compatibility with old testing."""
def fail(self, reason):
if isinstance(reason, Failure):
reason = reason.value
return pytest.fail(str(reason))
if request.instance:
request.instance.patch = monkeypatch.setattr
request.instance.config_dir = config_dir
request.instance.listen_port = listen_port
request.instance.id = lambda: request.node.name
request.cls.fail = fail
@pytest_twisted.async_yield_fixture(scope='function')
async def component(request):
"""Verify component registry is clean, and clean up after test."""
if len(_component._ComponentRegistry.components) != 0:
warnings.warn(
'The component._ComponentRegistry.components is not empty on test setup.\n'
'This is probably caused by another test that did not clean up after finishing!: %s'
% _component._ComponentRegistry.components
)
yield _component
await _component.shutdown()
_component._ComponentRegistry.components.clear()
_component._ComponentRegistry.dependents.clear()
@pytest_twisted.async_yield_fixture(scope='function')
async def base_fixture(common_fixture, component, request):
"""This fixture is autoused on all tests that subclass BaseTestCase"""
self = request.instance
if hasattr(self, 'set_up'):
try:
await maybeDeferred(self.set_up)
except Exception as exc:
warnings.warn('Error caught in test setup!\n%s' % exc)
pytest.fail('Error caught in test setup!\n%s' % exc)
yield
if hasattr(self, 'tear_down'):
try:
await maybeDeferred(self.tear_down)
except Exception as exc:
pytest.fail('Error caught in test teardown!\n%s' % exc)
@pytest.mark.usefixtures('base_fixture')
class BaseTestCase:
"""This is the base class that should be used for all test classes
that create classes that inherit from deluge.component.Component. It
ensures that the component registry has been cleaned up when tests
have finished.
"""
@pytest.fixture
def mock_mkstemp(tmp_path):
"""Return known tempfile location to verify file deleted"""
tmp_file = tempfile.mkstemp(dir=tmp_path)
with patch('tempfile.mkstemp', return_value=tmp_file):
yield tmp_file

View File

@ -1,142 +1,107 @@
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# alertmanager.py
#
# 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.
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
"""
The AlertManager handles all the libtorrent alerts.
"""The AlertManager handles all the libtorrent alerts."""
This should typically only be used by the Core. Plugins should utilize the
`:mod:EventManager` for similar functionality.
"""
import logging
from types import SimpleNamespace
from twisted.internet import reactor
import gobject
import deluge.component as component
from deluge._libtorrent import lt
from deluge.common import decode_bytes
log = logging.getLogger(__name__)
try:
import deluge.libtorrent as lt
except ImportError:
import libtorrent as lt
if not (lt.version_major == 0 and lt.version_minor == 14):
raise ImportError("This version of Deluge requires libtorrent 0.14!")
from deluge.log import LOG as log
class AlertManager(component.Component):
"""AlertManager fetches and processes libtorrent alerts"""
def __init__(self):
log.debug('AlertManager init...')
component.Component.__init__(self, 'AlertManager', interval=0.3)
self.session = component.get('Core').session
# Increase the alert queue size so that alerts don't get lost.
self.alert_queue_size = 10000
self.set_alert_queue_size(self.alert_queue_size)
alert_mask = (
lt.alert.category_t.error_notification
| lt.alert.category_t.port_mapping_notification
| lt.alert.category_t.storage_notification
| lt.alert.category_t.tracker_notification
| lt.alert.category_t.status_notification
| lt.alert.category_t.ip_block_notification
| lt.alert.category_t.performance_warning
| lt.alert.category_t.file_progress_notification
)
self.session.apply_settings({'alert_mask': alert_mask})
def __init__(self, session):
log.debug("AlertManager initialized..")
component.Component.__init__(self, "AlertManager", interval=50)
self.session = session
self.session.set_alert_mask(
lt.alert.category_t.error_notification |
lt.alert.category_t.port_mapping_notification |
lt.alert.category_t.storage_notification |
lt.alert.category_t.tracker_notification |
lt.alert.category_t.status_notification |
lt.alert.category_t.ip_block_notification)
# handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
self.handlers = {}
self.delayed_calls = []
def update(self):
self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()]
self.handle_alerts()
def stop(self):
for delayed_call in self.delayed_calls:
if delayed_call.active():
delayed_call.cancel()
self.delayed_calls = []
def shutdown(self):
del self.session
del self.handlers
def register_handler(self, alert_type, handler):
"""Registers a function that will be called when 'alert_type' is pop'd
in handle_alerts. The handler function should look like:
handler(alert)
Where 'alert' is the actual alert object from libtorrent
"""
Registers a function that will be called when 'alert_type' is pop'd
in handle_alerts. The handler function should look like: handler(alert)
Where 'alert' is the actual alert object from libtorrent.
:param alert_type: str, this is string representation of the alert name
:param handler: func(alert), the function to be called when the alert is raised
"""
if alert_type not in self.handlers:
if alert_type not in self.handlers.keys():
# There is no entry for this alert type yet, so lets make it with an
# empty list.
self.handlers[alert_type] = []
# Append the handler to the list in the handlers dictionary
self.handlers[alert_type].append(handler)
log.debug('Registered handler for alert %s', alert_type)
log.debug("Registered handler for alert %s", alert_type)
def deregister_handler(self, handler):
"""
De-registers the `:param:handler` function from all alert types.
:param handler: func, the handler function to deregister
"""
"""De-registers the 'handler' function from all alert types."""
# Iterate through all handlers and remove 'handler' where found
for (dummy_key, value) in self.handlers.items():
for (key, value) in self.handlers:
if handler in value:
# Handler is in this alert type list
value.remove(handler)
def handle_alerts(self):
"""
Pops all libtorrent alerts in the session queue and handles them appropriately.
"""
alerts = self.session.pop_alerts()
if not alerts:
return
num_alerts = len(alerts)
if log.isEnabledFor(logging.DEBUG):
log.debug('Alerts queued: %s', num_alerts)
if num_alerts > 0.9 * self.alert_queue_size:
log.warning(
'Warning total alerts queued, %s, passes 90%% of queue size.',
num_alerts,
)
# Loop through all alerts in the queue
for alert in alerts:
alert_type = type(alert).__name__
def handle_alerts(self, wait=False):
"""Pops all libtorrent alerts in the session queue and handles them
appropriately."""
alert = self.session.pop_alert()
while alert is not None:
# Loop through all alerts in the queue
# Do some magic to get the alert type as a string
alert_type = str(type(alert)).split("'")[1].split(".")[-1]
# Display the alert message
if log.isEnabledFor(logging.DEBUG):
log.debug('%s: %s', alert_type, decode_bytes(alert.message()))
log.debug("%s: %s", alert_type, alert.message())
# Call any handlers for this alert type
if alert_type in self.handlers:
if alert_type in self.handlers.keys():
for handler in self.handlers[alert_type]:
if log.isEnabledFor(logging.DEBUG):
log.debug('Handling alert: %s', alert_type)
# Copy alert attributes
alert_copy = SimpleNamespace(
**{
attr: getattr(alert, attr)
for attr in dir(alert)
if not attr.startswith('__')
}
)
self.delayed_calls.append(reactor.callLater(0, handler, alert_copy))
if not wait:
gobject.idle_add(handler, alert)
else:
handler(alert)
def set_alert_queue_size(self, queue_size):
"""Sets the maximum size of the libtorrent alert queue"""
log.info('Alert Queue Size set to %s', queue_size)
self.alert_queue_size = queue_size
component.get('Core').apply_session_setting(
'alert_queue_size', self.alert_queue_size
)
alert = self.session.pop_alert()
# Return True so that the timer continues
return True

View File

@ -1,285 +1,91 @@
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
# authmanager.py
#
# 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.
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import logging
import os
import shutil
import os.path
import random
import stat
import deluge.component as component
import deluge.configmanager as configmanager
from deluge.common import (
AUTH_LEVEL_ADMIN,
AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_NONE,
AUTH_LEVEL_NORMAL,
AUTH_LEVEL_READONLY,
create_localclient_account,
)
from deluge.error import AuthenticationRequired, AuthManagerError, BadLoginError
log = logging.getLogger(__name__)
AUTH_LEVELS_MAPPING = {
'NONE': AUTH_LEVEL_NONE,
'READONLY': AUTH_LEVEL_READONLY,
'DEFAULT': AUTH_LEVEL_DEFAULT,
'NORMAL': AUTH_LEVEL_NORMAL,
'ADMIN': AUTH_LEVEL_ADMIN,
}
AUTH_LEVELS_MAPPING_REVERSE = {v: k for k, v in AUTH_LEVELS_MAPPING.items()}
class Account:
__slots__ = ('username', 'password', 'authlevel')
def __init__(self, username, password, authlevel):
self.username = username
self.password = password
self.authlevel = authlevel
def data(self):
return {
'username': self.username,
'password': self.password,
'authlevel': AUTH_LEVELS_MAPPING_REVERSE[self.authlevel],
'authlevel_int': self.authlevel,
}
def __repr__(self):
return '<Account username="{username}" authlevel={authlevel}>'.format(
username=self.username,
authlevel=self.authlevel,
)
class AuthManager(component.Component):
def __init__(self):
component.Component.__init__(self, 'AuthManager', interval=10)
self.__auth = {}
self.__auth_modification_time = None
component.Component.__init__(self, "AuthManager")
self.auth = {}
def start(self):
self.__load_auth_file()
def stop(self):
self.__auth = {}
self.auth = {}
def shutdown(self):
pass
def update(self):
auth_file = configmanager.get_config_dir('auth')
# Check for auth file and create if necessary
if not os.path.isfile(auth_file):
log.info('Authfile not found, recreating it.')
self.__load_auth_file()
return
auth_file_modification_time = os.stat(auth_file).st_mtime
if self.__auth_modification_time != auth_file_modification_time:
log.info('Auth file changed, reloading it!')
self.__load_auth_file()
def authorize(self, username, password):
"""Authorizes users based on username and password.
"""
Authorizes users based on username and password
Args:
username (str): Username
password (str): Password
Returns:
int: The auth level for this user.
Raises:
AuthenticationRequired: If additional details are required to authenticate.
BadLoginError: If the username does not exist or password does not match.
:param username: str, username
:param password: str, password
:returns: True or False
:rtype: bool
"""
if not username:
raise AuthenticationRequired(
'Username and Password are required.', username
)
if username not in self.__auth:
if username not in self.auth:
# Let's try to re-load the file.. Maybe it's been updated
self.__load_auth_file()
if username not in self.__auth:
raise BadLoginError('Username does not exist', username)
if username not in self.auth:
return False
if self.__auth[username].password == password:
# Return the users auth level
return self.__auth[username].authlevel
elif not password and self.__auth[username].password:
raise AuthenticationRequired('Password is required', username)
else:
raise BadLoginError('Password does not match', username)
def has_account(self, username):
return username in self.__auth
def get_known_accounts(self):
"""Returns a list of known deluge usernames."""
self.__load_auth_file()
return [account.data() for account in self.__auth.values()]
def create_account(self, username, password, authlevel):
if username in self.__auth:
raise AuthManagerError('Username in use.', username)
if authlevel not in AUTH_LEVELS_MAPPING:
raise AuthManagerError('Invalid auth level: %s' % authlevel)
try:
self.__auth[username] = Account(
username, password, AUTH_LEVELS_MAPPING[authlevel]
)
self.write_auth_file()
if self.auth[username] == password:
return True
except Exception as ex:
log.exception(ex)
raise ex
def update_account(self, username, password, authlevel):
if username not in self.__auth:
raise AuthManagerError('Username not known', username)
if authlevel not in AUTH_LEVELS_MAPPING:
raise AuthManagerError('Invalid auth level: %s' % authlevel)
try:
self.__auth[username].username = username
self.__auth[username].password = password
self.__auth[username].authlevel = AUTH_LEVELS_MAPPING[authlevel]
self.write_auth_file()
return True
except Exception as ex:
log.exception(ex)
raise ex
def remove_account(self, username):
if username not in self.__auth:
raise AuthManagerError('Username not known', username)
elif username == component.get('RPCServer').get_session_user():
raise AuthManagerError(
'You cannot delete your own account while logged in!', username
)
del self.__auth[username]
self.write_auth_file()
return True
def write_auth_file(self):
filename = 'auth'
filepath = os.path.join(configmanager.get_config_dir(), filename)
filepath_bak = filepath + '.bak'
filepath_tmp = filepath + '.tmp'
try:
if os.path.isfile(filepath):
log.debug('Creating backup of %s at: %s', filename, filepath_bak)
shutil.copy2(filepath, filepath_bak)
except OSError as ex:
log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex)
else:
log.info('Saving the %s at: %s', filename, filepath)
try:
with open(filepath_tmp, 'w', encoding='utf8') as _file:
for account in self.__auth.values():
_file.write(
'%(username)s:%(password)s:%(authlevel_int)s\n'
% account.data()
)
_file.flush()
os.fsync(_file.fileno())
shutil.move(filepath_tmp, filepath)
except OSError as ex:
log.error('Unable to save %s: %s', filename, ex)
if os.path.isfile(filepath_bak):
log.info('Restoring backup of %s from: %s', filename, filepath_bak)
shutil.move(filepath_bak, filepath)
self.__load_auth_file()
return False
def __load_auth_file(self):
save_and_reload = False
filename = 'auth'
auth_file = configmanager.get_config_dir(filename)
auth_file_bak = auth_file + '.bak'
auth_file = configmanager.get_config_dir("auth")
# Check for auth file and create if necessary
if not os.path.isfile(auth_file):
create_localclient_account()
return self.__load_auth_file()
auth_file_modification_time = os.stat(auth_file).st_mtime
if self.__auth_modification_time is None:
self.__auth_modification_time = auth_file_modification_time
elif self.__auth_modification_time == auth_file_modification_time:
# File didn't change, no need for re-parsing's
return
for _filepath in (auth_file, auth_file_bak):
log.info('Opening %s for load: %s', filename, _filepath)
if not os.path.exists(auth_file):
# We create a 'localclient' account with a random password
try:
with open(_filepath, encoding='utf8') as _file:
file_data = _file.readlines()
except OSError as ex:
log.warning('Unable to load %s: %s', _filepath, ex)
file_data = []
else:
log.info('Successfully loaded %s: %s', filename, _filepath)
break
from hashlib import sha1 as sha_hash
except ImportError:
from sha import new as sha_hash
open(auth_file, "w").write("localclient:" + sha_hash(str(random.random())).hexdigest() + "\n")
# Change the permissions on the file so only this user can read/write it
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)
# Load the auth file into a dictionary: {username: Account(...)}
for line in file_data:
line = line.strip()
if line.startswith('#') or not line:
# This line is a comment or empty
# Load the auth file into a dictionary: {username: password, ...}
f = open(auth_file, "r")
for line in f:
if line.startswith("#"):
# This is a comment line
continue
lsplit = line.split(':')
if len(lsplit) == 2:
username, password = lsplit
log.warning(
'Your auth entry for %s contains no auth level, '
'using AUTH_LEVEL_DEFAULT(%s)..',
username,
AUTH_LEVEL_DEFAULT,
)
if username == 'localclient':
authlevel = AUTH_LEVEL_ADMIN
else:
authlevel = AUTH_LEVEL_DEFAULT
# This is probably an old auth file
save_and_reload = True
elif len(lsplit) == 3:
username, password, authlevel = lsplit
else:
log.error('Your auth file is malformed: Incorrect number of fields!')
continue
username = username.strip()
password = password.strip()
try:
authlevel = int(authlevel)
username, password = line.split(":")
except ValueError:
try:
authlevel = AUTH_LEVELS_MAPPING[authlevel]
except KeyError:
log.error(
'Your auth file is malformed: %r is not a valid auth level',
authlevel,
)
continue
self.__auth[username] = Account(username, password, authlevel)
if 'localclient' not in self.__auth:
create_localclient_account(True)
return self.__load_auth_file()
if save_and_reload:
log.info('Re-writing auth file (upgrade)')
self.write_auth_file()
self.__auth_modification_time = auth_file_modification_time
self.auth[username.strip()] = password.strip()

125
deluge/core/autoadd.py Normal file
View File

@ -0,0 +1,125 @@
#
# autoadd.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import os
try:
import deluge.libtorrent as lt
except ImportError:
import libtorrent as lt
if not (lt.version_major == 0 and lt.version_minor == 14):
raise ImportError("This version of Deluge requires libtorrent 0.14!")
import deluge.component as component
from deluge.configmanager import ConfigManager
from deluge.log import LOG as log
MAX_NUM_ATTEMPTS = 10
class AutoAdd(component.Component):
def __init__(self):
component.Component.__init__(self, "AutoAdd", depend=["TorrentManager"], interval=5000)
# Get the core config
self.config = ConfigManager("core.conf")
# A list of filenames
self.invalid_torrents = []
# Filename:Attempts
self.attempts = {}
# Register set functions
self.config.register_set_function("autoadd_enable",
self._on_autoadd_enable, apply_now=True)
self.config.register_set_function("autoadd_location",
self._on_autoadd_location)
def update(self):
if not self.config["autoadd_enable"]:
# We shouldn't be updating because autoadd is not enabled
component.pause("AutoAdd")
return
# Check the auto add folder for new torrents to add
if not os.path.exists(self.config["autoadd_location"]):
log.warning("Invalid AutoAdd folder: %s", self.config["autoadd_location"])
component.pause("AutoAdd")
return
for filename in os.listdir(self.config["autoadd_location"]):
if filename.split(".")[-1] == "torrent":
filepath = os.path.join(self.config["autoadd_location"], filename)
try:
filedump = self.load_torrent(filepath)
except (RuntimeError, Exception), e:
# If the torrent is invalid, we keep track of it so that we
# can try again on the next pass. This is because some
# torrents may not be fully saved during the pass.
log.debug("Torrent is invalid: %s", e)
if filename in self.invalid_torrents:
self.attempts[filename] += 1
if self.attempts[filename] >= MAX_NUM_ATTEMPTS:
os.rename(filepath, filepath + ".invalid")
del self.attempts[filename]
self.invalid_torrents.remove(filename)
else:
self.invalid_torrents.append(filename)
self.attempts[filename] = 1
continue
# The torrent looks good, so lets add it to the session
component.get("TorrentManager").add(filedump=filedump, filename=filename)
os.remove(filepath)
def load_torrent(self, filename):
try:
log.debug("Attempting to open %s for add.", filename)
_file = open(filename, "rb")
filedump = _file.read()
if not filedump:
raise RuntimeError, "Torrent is 0 bytes!"
_file.close()
except IOError, e:
log.warning("Unable to open %s: %s", filename, e)
raise e
# Get the info to see if any exceptions are raised
info = lt.torrent_info(lt.bdecode(filedump))
return filedump
def _on_autoadd_enable(self, key, value):
log.debug("_on_autoadd_enable")
if value:
component.resume("AutoAdd")
else:
component.pause("AutoAdd")
def _on_autoadd_location(self, key, value):
log.debug("_on_autoadd_location")
# We need to resume the component just incase it was paused due to
# an invalid autoadd location.
if self.config["autoadd_enable"]:
component.resume("AutoAdd")

File diff suppressed because it is too large Load Diff

View File

@ -1,203 +1,45 @@
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# daemon.py
#
# 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.
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
"""The Deluge daemon"""
import logging
import os
import socket
from twisted.internet import reactor
import deluge.component as component
from deluge.common import get_version, is_ip, is_process_running, windows_check
from deluge.configmanager import get_config_dir
from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer, export
from deluge.error import DaemonRunningError
if windows_check():
from win32api import SetConsoleCtrlHandler
from win32con import CTRL_CLOSE_EVENT, CTRL_SHUTDOWN_EVENT
log = logging.getLogger(__name__)
def is_daemon_running(pid_file):
"""
Check for another running instance of the daemon using the same pid file.
Args:
pid_file: The location of the file with pid, port values.
Returns:
bool: True is daemon is running, False otherwise.
"""
try:
with open(pid_file) as _file:
pid, port = (int(x) for x in _file.readline().strip().split(';'))
except (OSError, ValueError):
return False
if is_process_running(pid):
# Ensure it's a deluged process by trying to open a socket to it's port.
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
_socket.connect(('127.0.0.1', port))
except OSError:
# Can't connect, so pid is not a deluged process.
return False
else:
# This is a deluged process!
_socket.close()
return True
import deluge.configmanager
import deluge.common
from deluge.log import LOG as log
class Daemon:
"""The Deluge Daemon class"""
def __init__(self, options, args):
version = deluge.common.get_version()
if deluge.common.get_revision() != "":
version = version + "r" + deluge.common.get_revision()
def __init__(
self,
listen_interface=None,
outgoing_interface=None,
interface=None,
port=None,
standalone=False,
read_only_config_keys=None,
):
"""
Args:
listen_interface (str, optional): The IP address to listen to
BitTorrent connections on.
outgoing_interface (str, optional): The network interface name or
IP address to open outgoing BitTorrent connections on.
interface (str, optional): The IP address the daemon will
listen for UI connections on.
port (int, optional): The port the daemon will listen for UI
connections on.
standalone (bool, optional): If True the client is in Standalone
mode otherwise, if False, start the daemon as separate process.
read_only_config_keys (list of str, optional): A list of config
keys that will not be altered by core.set_config() RPC method.
"""
self.standalone = standalone
self.pid_file = get_config_dir('deluged.pid')
log.info('Deluge daemon %s', get_version())
if is_daemon_running(self.pid_file):
raise DaemonRunningError(
'Deluge daemon already running with this config directory!'
)
# Twisted catches signals to terminate, so just have it call the shutdown method.
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
# Catch some Windows specific signals
if windows_check():
def win_handler(ctrl_type):
"""Handle the Windows shutdown or close events."""
log.debug('windows handler ctrl_type: %s', ctrl_type)
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
self._shutdown()
return 1
SetConsoleCtrlHandler(win_handler)
log.info("Deluge daemon %s", version)
log.debug("options: %s", options)
log.debug("args: %s", args)
# Set the config directory
deluge.configmanager.set_config_dir(options.config)
from deluge.core.core import Core
# Start the core as a thread and join it until it's done
self.core = Core(
listen_interface=listen_interface,
outgoing_interface=outgoing_interface,
read_only_config_keys=read_only_config_keys,
)
self.core = Core(options.port).run()
if port is None:
port = self.core.config['daemon_port']
self.port = port
if interface and not is_ip(interface):
log.error('Invalid UI interface (must be IP Address): %s', interface)
interface = None
self.rpcserver = RPCServer(
port=port,
allow_remote=self.core.config['allow_remote'],
listen=not standalone,
interface=interface,
)
log.debug(
'Listening to UI on: %s:%s and bittorrent on: %s Making connections out on: %s',
interface,
port,
listen_interface,
outgoing_interface,
)
def start(self):
# Register the daemon and the core RPCs
self.rpcserver.register_object(self.core)
self.rpcserver.register_object(self)
# Make sure we start the PreferencesManager first
component.start('PreferencesManager')
if not self.standalone:
log.info('Deluge daemon starting...')
# Create pid file to track if deluged is running, also includes the port number.
pid = os.getpid()
log.debug('Storing pid %s & port %s in: %s', pid, self.port, self.pid_file)
with open(self.pid_file, 'w') as _file:
_file.write(f'{pid};{self.port}\n')
component.start()
try:
reactor.run()
finally:
log.debug('Remove pid file: %s', self.pid_file)
os.remove(self.pid_file)
log.info('Deluge daemon shutdown successfully')
@export()
def shutdown(self, *args, **kwargs):
log.debug('Deluge daemon shutdown requested...')
reactor.callLater(0, reactor.stop)
def _shutdown(self, *args, **kwargs):
log.info('Deluge daemon shutting down, waiting for components to shutdown...')
if not self.standalone:
return component.shutdown()
@export()
def get_method_list(self):
"""Returns a list of the exported methods."""
return self.rpcserver.get_method_list()
@export()
def get_version(self):
"""Returns the daemon version"""
return get_version()
@export(1)
def authorized_call(self, rpc):
"""Determines if session auth_level is authorized to call RPC.
Args:
rpc (str): A RPC, e.g. core.get_torrents_status
Returns:
bool: True if authorized to call RPC, otherwise False.
"""
if rpc not in self.get_method_list():
return False
return (
self.rpcserver.get_session_auth_level()
>= self.rpcserver.get_rpc_auth_level(rpc)
)

View File

@ -1,140 +0,0 @@
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me>
#
# 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
import sys
from logging import DEBUG, FileHandler, getLogger
from twisted.internet.error import CannotListenError
from deluge.argparserbase import ArgParserBase
from deluge.common import run_profiled
from deluge.configmanager import get_config_dir
from deluge.i18n import setup_mock_translation
def add_daemon_options(parser):
group = parser.add_argument_group(_('Daemon Options'))
group.add_argument(
'-u',
'--ui-interface',
metavar='<ip-addr>',
action='store',
help=_('IP address to listen for UI connections'),
)
group.add_argument(
'-p',
'--port',
metavar='<port>',
action='store',
type=int,
help=_('Port to listen for UI connections on'),
)
group.add_argument(
'-i',
'--interface',
metavar='<ip-addr>',
dest='listen_interface',
action='store',
help=_('IP address to listen for BitTorrent connections'),
)
group.add_argument(
'-o',
'--outgoing-interface',
metavar='<interface>',
dest='outgoing_interface',
action='store',
help=_(
'The network interface name or IP address for outgoing BitTorrent connections.'
),
)
group.add_argument(
'--read-only-config-keys',
metavar='<comma-separated-keys>',
action='store',
help=_('Config keys to be unmodified by `set_config` RPC'),
type=str,
default='',
)
parser.add_process_arg_group()
def start_daemon(skip_start=False):
"""
Entry point for daemon script
Args:
skip_start (bool): If starting daemon should be skipped.
Returns:
deluge.core.daemon.Daemon: A new daemon object
"""
setup_mock_translation()
# Setup the argument parser
parser = ArgParserBase()
add_daemon_options(parser)
options = parser.parse_args()
# Check for any daemons running with this same config
from deluge.core.daemon import is_daemon_running
pid_file = get_config_dir('deluged.pid')
if is_daemon_running(pid_file):
print(
'Cannot run multiple daemons with same config directory.\n'
'If you believe this is an error, force starting by deleting: %s' % pid_file
)
sys.exit(1)
log = getLogger(__name__)
# If no logfile specified add logging to default location (as well as stdout)
if not options.logfile:
options.logfile = get_config_dir('deluged.log')
file_handler = FileHandler(options.logfile)
log.addHandler(file_handler)
def run_daemon(options):
try:
from deluge.core.daemon import Daemon
daemon = Daemon(
listen_interface=options.listen_interface,
outgoing_interface=options.outgoing_interface,
interface=options.ui_interface,
port=options.port,
read_only_config_keys=options.read_only_config_keys.split(','),
)
if skip_start:
return daemon
else:
daemon.start()
except CannotListenError as ex:
log.error(
'Cannot start deluged, listen port in use.\n'
' Check for other running daemons or services using this port: %s:%s',
ex.interface,
ex.port,
)
sys.exit(1)
except Exception as ex:
log.error('Unable to start deluged: %s', ex)
if log.isEnabledFor(DEBUG):
log.exception(ex)
sys.exit(1)
finally:
log.info('Exiting...')
if options.pidfile:
os.remove(options.pidfile)
return run_profiled(
run_daemon, options, output_file=options.profile, do_profile=options.profile
)

View File

@ -1,66 +0,0 @@
#
# Copyright (C) 2009 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.
#
import logging
import deluge.component as component
log = logging.getLogger(__name__)
class EventManager(component.Component):
def __init__(self):
component.Component.__init__(self, 'EventManager')
self.handlers = {}
def emit(self, event):
"""
Emits the event to interested clients.
:param event: DelugeEvent
"""
# Emit the event to the interested clients
component.get('RPCServer').emit_event(event)
# Call any handlers for the event
if event.name in self.handlers:
for handler in self.handlers[event.name]:
# log.debug('Running handler %s for event %s with args: %s', event.name, handler, event.args)
try:
handler(*event.args)
except Exception as ex:
log.error(
'Event handler %s failed in %s with exception %s',
event.name,
handler,
ex,
)
def register_event_handler(self, event, handler):
"""
Registers a function to be called when a `:param:event` is emitted.
:param event: str, the event name
:param handler: function, to be called when `:param:event` is emitted
"""
if event not in self.handlers:
self.handlers[event] = []
if handler not in self.handlers[event]:
self.handlers[event].append(handler)
def deregister_event_handler(self, event, handler):
"""
Deregisters an event handler function.
:param event: str, the event name
:param handler: function, currently registered to handle `:param:event`
"""
if event in self.handlers and handler in self.handlers[event]:
self.handlers[event].remove(handler)

View File

@ -1,125 +1,87 @@
#
# core.py
#
# 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.
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import logging
import deluge.component as component
from deluge.common import TORRENT_STATE
from deluge.log import LOG as log
log = logging.getLogger(__name__)
STATE_SORT = ["All", "Downloading", "Seeding", "Active", "Paused", "Queued"]
STATE_SORT = ['All', 'Active'] + TORRENT_STATE
# Special purpose filters:
#special purpose filters:
def filter_keywords(torrent_ids, values):
# Cleanup
keywords = ','.join([v.lower() for v in values])
keywords = keywords.split(',')
#cleanup.
keywords = ",".join([v.lower() for v in values])
keywords = keywords.split(",")
for keyword in keywords:
torrent_ids = filter_one_keyword(torrent_ids, keyword)
return torrent_ids
def filter_one_keyword(torrent_ids, keyword):
"""
search torrent on keyword.
searches title,state,tracker-status,tracker,files
"""
all_torrents = component.get('TorrentManager').torrents
all_torrents = component.get("TorrentManager").torrents
#filter:
found = False
for torrent_id in torrent_ids:
torrent = all_torrents[torrent_id]
if keyword in torrent.filename.lower():
yield torrent_id
elif keyword in torrent.state.lower():
yield torrent_id
elif torrent.trackers and keyword in torrent.trackers[0]['url']:
elif torrent.trackers and keyword in torrent.trackers[0]["url"]:
yield torrent_id
elif keyword in torrent_id:
yield torrent_id
# Want to find broken torrents (search on "error", or "unregistered")
#i want to find broken torrents (search on "error", or "unregistered")
elif keyword in torrent.tracker_status.lower():
yield torrent_id
else:
for t_file in torrent.get_files():
if keyword in t_file['path'].lower():
if keyword in t_file["path"].lower():
yield torrent_id
break
def filter_by_name(torrent_ids, search_string):
all_torrents = component.get('TorrentManager').torrents
try:
search_string, match_case = search_string[0].split('::match')
except ValueError:
search_string = search_string[0]
match_case = False
if match_case is False:
search_string = search_string.lower()
for torrent_id in torrent_ids:
torrent_name = all_torrents[torrent_id].get_name()
if match_case is False:
torrent_name = all_torrents[torrent_id].get_name().lower()
else:
torrent_name = all_torrents[torrent_id].get_name()
if search_string in torrent_name:
yield torrent_id
def tracker_error_filter(torrent_ids, values):
filtered_torrent_ids = []
tm = component.get('TorrentManager')
# If this is a tracker_host, then we need to filter on it
if values[0] != 'Error':
for torrent_id in torrent_ids:
if values[0] == tm[torrent_id].get_status(['tracker_host'])['tracker_host']:
filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids
# Check torrent's tracker_status for 'Error:' and return those torrent_ids
for torrent_id in torrent_ids:
if 'Error:' in tm[torrent_id].get_status(['tracker_status'])['tracker_status']:
filtered_torrent_ids.append(torrent_id)
return filtered_torrent_ids
class FilterManager(component.Component):
"""FilterManager"""
"""FilterManager
"""
def __init__(self, core):
component.Component.__init__(self, 'FilterManager')
log.debug('FilterManager init..')
component.Component.__init__(self, "FilterManager")
log.debug("FilterManager init..")
self.core = core
self.torrents = core.torrentmanager
self.torrents = core.torrents
self.registered_filters = {}
self.register_filter('keyword', filter_keywords)
self.register_filter('name', filter_by_name)
self.register_filter("keyword", filter_keywords)
self.tree_fields = {}
self.register_tree_field('state', self._init_state_tree)
def _init_tracker_tree():
return {'Error': 0}
self.register_tree_field('tracker_host', _init_tracker_tree)
self.register_filter('tracker_host', tracker_error_filter)
def _init_users_tree():
return {'': 0}
self.register_tree_field('owner', _init_users_tree)
self.register_tree_field("state", self._init_state_tree)
self.register_tree_field("tracker_host")
def filter_torrent_ids(self, filter_dict):
"""
@ -129,61 +91,51 @@ class FilterManager(component.Component):
if not filter_dict:
return self.torrents.get_torrent_list()
# Sanitize input: filter-value must be a list of strings
#sanitize input: filter-value must be a list of strings
for key, value in filter_dict.items():
if isinstance(value, str):
filter_dict[key] = [value]
filter_dict[key] = [value]
# Optimized filter for id
if 'id' in filter_dict:
torrent_ids = list(filter_dict['id'])
del filter_dict['id']
if "id"in filter_dict: #optimized filter for id:
torrent_ids = filter_dict["id"]
del filter_dict["id"]
else:
torrent_ids = self.torrents.get_torrent_list()
# Return if there's nothing more to filter
if not filter_dict:
if not filter_dict: #return if there's nothing more to filter
return torrent_ids
# Special purpose, state=Active.
if 'state' in filter_dict:
# We need to make sure this is a list for the logic below
filter_dict['state'] = list(filter_dict['state'])
if 'state' in filter_dict and 'Active' in filter_dict['state']:
filter_dict['state'].remove('Active')
if not filter_dict['state']:
del filter_dict['state']
#special purpose: state=Active.
if "state" in filter_dict and "Active" in filter_dict["state"]:
filter_dict["state"].remove("Active")
if not filter_dict["state"]:
del filter_dict["state"]
torrent_ids = self.filter_state_active(torrent_ids)
if not filter_dict:
if not filter_dict: #return if there's nothing more to filter
return torrent_ids
# Registered filters
for field, values in list(filter_dict.items()):
#Registered filters:
for field, values in filter_dict.items():
if field in self.registered_filters:
# Filters out doubles
torrent_ids = list(
set(self.registered_filters[field](torrent_ids, values))
)
# a set filters out the doubles.
torrent_ids = list(set(self.registered_filters[field](torrent_ids, values)))
del filter_dict[field]
if not filter_dict:
if not filter_dict: #return if there's nothing more to filter
return torrent_ids
torrent_keys, plugin_keys = self.torrents.separate_keys(
list(filter_dict), torrent_ids
)
# Leftover filter arguments, default filter on status fields.
#leftover filter arguments:
#default filter on status fields.
status_func = self.core.export_get_torrent_status #premature optimalisation..
for torrent_id in list(torrent_ids):
status = self.core.create_torrent_status(
torrent_id, torrent_keys, plugin_keys
)
for field, values in filter_dict.items():
if field in status and status[field] in values:
continue
elif torrent_id in torrent_ids:
status = status_func(torrent_id, filter_dict.keys()) #status={key:value}
for field, values in filter_dict.iteritems():
if (not status[field] in values) and torrent_id in torrent_ids:
torrent_ids.remove(torrent_id)
return torrent_ids
def get_filter_tree(self, show_zero_hits=True, hide_cat=None):
@ -192,83 +144,85 @@ class FilterManager(component.Component):
for use in sidebar.
"""
torrent_ids = self.torrents.get_torrent_list()
tree_keys = list(self.tree_fields)
status_func = self.core.export_get_torrent_status #premature optimalisation..
tree_keys = list(self.tree_fields.keys())
if hide_cat:
for cat in hide_cat:
tree_keys.remove(cat)
torrent_keys, plugin_keys = self.torrents.separate_keys(tree_keys, torrent_ids)
items = {field: self.tree_fields[field]() for field in tree_keys}
items = dict( (field, self.tree_fields[field]()) for field in tree_keys)
#count status fields.
for torrent_id in list(torrent_ids):
status = self.core.create_torrent_status(
torrent_id, torrent_keys, plugin_keys
) # status={key:value}
status = status_func(torrent_id, tree_keys) #status={key:value}
for field in tree_keys:
value = status[field]
items[field][value] = items[field].get(value, 0) + 1
if 'tracker_host' in items:
items['tracker_host']['All'] = len(torrent_ids)
items['tracker_host']['Error'] = len(
tracker_error_filter(torrent_ids, ('Error',))
)
if "state" in tree_keys and not show_zero_hits:
self._hide_state_items(items["state"])
if not show_zero_hits:
for cat in ['state', 'owner', 'tracker_host']:
if cat in tree_keys:
self._hide_state_items(items[cat])
#return a dict of tuples:
sorted_items = {}
for field in tree_keys:
sorted_items[field] = sorted(items[field].iteritems())
# Return a dict of tuples:
sorted_items = {field: sorted(items[field].items()) for field in tree_keys}
if 'state' in tree_keys:
sorted_items['state'].sort(key=self._sort_state_item)
if "state" in tree_keys:
sorted_items["state"].sort(self._sort_state_items)
return sorted_items
def _init_state_tree(self):
init_state = {}
init_state['All'] = len(self.torrents.get_torrent_list())
for state in TORRENT_STATE:
init_state[state] = 0
init_state['Active'] = len(
self.filter_state_active(self.torrents.get_torrent_list())
)
return init_state
return {"All":len(self.torrents.get_torrent_list()),
"Downloading":0,
"Seeding":0,
"Paused":0,
"Checking":0,
"Queued":0,
"Error":0,
"Active":len(self.filter_state_active(self.torrents.get_torrent_list()))
}
def register_filter(self, filter_id, filter_func, filter_value=None):
self.registered_filters[filter_id] = filter_func
def register_filter(self, id, filter_func, filter_value = None):
self.registered_filters[id] = filter_func
def deregister_filter(self, filter_id):
del self.registered_filters[filter_id]
def deregister_filter(self, id):
del self.registered_filters[id]
def register_tree_field(self, field, init_func=lambda: {}):
def register_tree_field(self, field, init_func = lambda : {}):
self.tree_fields[field] = init_func
def deregister_tree_field(self, field):
if field in self.tree_fields:
del self.tree_fields[field]
del self.tree_fields[field]
def filter_state_active(self, torrent_ids):
get_status = self.core.export_get_torrent_status
for torrent_id in list(torrent_ids):
status = self.torrents[torrent_id].get_status(
['download_payload_rate', 'upload_payload_rate']
)
if status['download_payload_rate'] or status['upload_payload_rate']:
pass
status = get_status(torrent_id, ["download_payload_rate", "upload_payload_rate"])
if status["download_payload_rate"] or status["upload_payload_rate"]:
pass #ok
else:
torrent_ids.remove(torrent_id)
return torrent_ids
def _hide_state_items(self, state_items):
"""For hide(show)-zero hits"""
for value, count in list(state_items.items()):
if value != 'All' and count == 0:
"for hide(show)-zero hits"
for (value, count) in state_items.items():
if value != "All" and count == 0:
del state_items[value]
def _sort_state_item(self, item):
try:
return STATE_SORT.index(item[0])
except ValueError:
return 99
def _sort_state_items(self, x, y):
""
if x[0] in STATE_SORT:
ix = STATE_SORT.index(x[0])
else:
ix = 99
if y[0] in STATE_SORT:
iy = STATE_SORT.index(y[0])
else:
iy = 99
return ix - iy

View File

@ -0,0 +1,135 @@
#
# oldstateupgrader.py
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import os
import os.path
import pickle
import cPickle
import shutil
try:
import deluge.libtorrent as lt
except ImportError:
import libtorrent as lt
if not (lt.version_major == 0 and lt.version_minor == 14):
raise ImportError("This version of Deluge requires libtorrent 0.14!")
from deluge.configmanager import ConfigManager
import deluge.core.torrentmanager
from deluge.log import LOG as log
#start : http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286203
def makeFakeClass(module, name):
class FakeThing(object):
pass
FakeThing.__name__ = name
FakeThing.__module__ = '(fake)' + module
return FakeThing
class PickleUpgrader(pickle.Unpickler):
def find_class(self, module, cname):
# Pickle tries to load a couple things like copy_reg and
# __builtin__.object even though a pickle file doesn't
# explicitly reference them (afaict): allow them to be loaded
# normally.
if module in ('copy_reg', '__builtin__'):
thing = pickle.Unpickler.find_class(self, module, cname)
return thing
return makeFakeClass(module, cname)
# end: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286203
class OldStateUpgrader:
def __init__(self):
self.config = ConfigManager("core.conf")
self.state05_location = os.path.join(self.config["config_location"], "persistent.state")
self.state10_location = os.path.join(self.config["state_location"], "torrents.state")
if os.path.exists(self.state05_location) and not os.path.exists(self.state10_location):
# If the 0.5 state file exists and the 1.0 doesn't, then let's upgrade it
self.upgrade05()
def upgrade05(self):
try:
state = PickleUpgrader(open(self.state05_location, "rb")).load()
except Exception, e:
log.debug("Unable to open 0.5 state file: %s", e)
return
# Check to see if we can upgrade this file
if type(state).__name__ == 'list':
log.warning("0.5 state file is too old to upgrade")
return
new_state = deluge.core.torrentmanager.TorrentManagerState()
for ti, uid in state.torrents.items():
torrent_path = os.path.join(self.config["config_location"], "torrentfiles", ti.filename)
try:
torrent_info = None
log.debug("Attempting to create torrent_info from %s", torrent_path)
_file = open(torrent_path, "rb")
torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
_file.close()
except (IOError, RuntimeError), e:
log.warning("Unable to open %s: %s", filepath, e)
# Copy the torrent file to the new location
import shutil
shutil.copyfile(torrent_path, os.path.join(self.config["state_location"], str(torrent_info.info_hash()) + ".torrent"))
# Set the file prioritiy property if not already there
if not hasattr(ti, "priorities"):
ti.priorities = [1] * torrent_info.num_files()
# Create the new TorrentState object
new_torrent = deluge.core.torrentmanager.TorrentState(
torrent_id=str(torrent_info.info_hash()),
filename=ti.filename,
save_path=ti.save_dir,
compact=ti.compact,
paused=ti.user_paused,
total_uploaded=ti.uploaded_memory,
max_upload_speed=ti.upload_rate_limit,
max_download_speed=ti.download_rate_limit,
file_priorities=ti.priorities,
queue=state.queue.index(ti)
)
# Append the object to the state list
new_state.torrents.append(new_torrent)
# Now we need to write out the new state file
try:
log.debug("Saving torrent state file.")
state_file = open(
os.path.join(self.config["state_location"], "torrents.state"), "wb")
cPickle.dump(new_state, state_file)
state_file.close()
except IOError, e:
log.warning("Unable to save state file: %s", e)
return
# Rename the persistent.state file
try:
os.rename(self.state05_location, self.state05_location + ".old")
except Exception, e:
log.debug("Unable to rename old persistent.state file! %s", e)

View File

@ -1,105 +1,154 @@
#
# pluginmanager.py
#
# 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.
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
"""PluginManager for Core"""
import logging
from twisted.internet import defer
import gobject
import deluge.component as component
import deluge.pluginmanagerbase
from deluge.event import PluginDisabledEvent, PluginEnabledEvent
import deluge.component as component
from deluge.log import LOG as log
log = logging.getLogger(__name__)
class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Component):
class PluginManager(deluge.pluginmanagerbase.PluginManagerBase,
component.Component):
"""PluginManager handles the loading of plugins and provides plugins with
functions to access parts of the core."""
def __init__(self, core):
component.Component.__init__(self, 'CorePluginManager')
component.Component.__init__(self, "PluginManager")
self.core = core
# Set up the hooks dictionary
self.hooks = {
"post_torrent_add": [],
"post_torrent_remove": [],
"post_session_load": []
}
self.status_fields = {}
# Call the PluginManagerBase constructor
deluge.pluginmanagerbase.PluginManagerBase.__init__(
self, 'core.conf', 'deluge.plugin.core'
)
self, "core.conf", "deluge.plugin.core")
def start(self):
# Enable plugins that are enabled in the config
self.enable_plugins()
# Set update timer to call update() in plugins every second
self.update_timer = gobject.timeout_add(1000, self.update_plugins)
def stop(self):
# Disable all enabled plugins
self.disable_plugins()
# Stop the update timer
gobject.source_remove(self.update_timer)
def shutdown(self):
self.stop()
def update_plugins(self):
for plugin in self.plugins:
if hasattr(self.plugins[plugin], 'update'):
try:
self.plugins[plugin].update()
except Exception as ex:
log.exception(ex)
def enable_plugin(self, name):
d = defer.succeed(True)
if name not in self.plugins:
d = deluge.pluginmanagerbase.PluginManagerBase.enable_plugin(self, name)
def on_enable_plugin(result):
if result is True and name in self.plugins:
component.get('EventManager').emit(PluginEnabledEvent(name))
return result
d.addBoth(on_enable_plugin)
return d
def disable_plugin(self, name):
d = defer.succeed(True)
if name in self.plugins:
d = deluge.pluginmanagerbase.PluginManagerBase.disable_plugin(self, name)
def on_disable_plugin(result):
if name not in self.plugins:
component.get('EventManager').emit(PluginDisabledEvent(name))
return result
d.addBoth(on_disable_plugin)
return d
def get_status(self, torrent_id, fields):
"""Return the value of status fields for the selected torrent_id."""
status = {}
if len(fields) == 0:
fields = list(self.status_fields)
for field in fields:
for plugin in self.plugins.keys():
try:
status[field] = self.status_fields[field](torrent_id)
except KeyError:
self.plugins[plugin].update()
except AttributeError:
# We don't care if this doesn't work
pass
return status
def get_core(self):
"""Returns a reference to the core"""
return self.core
def register_status_field(self, field, function):
"""Register a new status field. This can be used in the same way the
client requests other status information from core."""
log.debug('Registering status field %s with PluginManager', field)
log.debug("Registering status field %s with PluginManager", field)
self.status_fields[field] = function
def deregister_status_field(self, field):
"""Deregisters a status field"""
log.debug('Deregistering status field %s with PluginManager', field)
log.debug("Deregistering status field %s with PluginManager", field)
try:
del self.status_fields[field]
except Exception:
log.warning('Unable to deregister status field %s', field)
except:
log.warning("Unable to deregister status field %s", field)
def get_status(self, torrent_id, fields):
"""Return the value of status fields for the selected torrent_id."""
status = {}
for field in fields:
try:
status[field] = self.status_fields[field](torrent_id)
except KeyError:
log.warning("Status field %s is not registered with the\
PluginManager.", field)
return status
def register_hook(self, hook, function):
"""Register a hook function with the plugin manager"""
try:
self.hooks[hook].append(function)
except KeyError:
log.warning("Plugin attempting to register invalid hook.")
def deregister_hook(self, hook, function):
"""Deregisters a hook function"""
try:
self.hooks[hook].remove(function)
except:
log.warning("Unable to deregister hook %s", hook)
def run_post_torrent_add(self, torrent_id):
"""This hook is run after a torrent has been added to the session."""
log.debug("run_post_torrent_add")
for function in self.hooks["post_torrent_add"]:
function(torrent_id)
def run_post_torrent_remove(self, torrent_id):
"""This hook is run after a torrent has been removed from the session.
"""
log.debug("run_post_torrent_remove")
for function in self.hooks["post_torrent_remove"]:
function(torrent_id)
def run_post_session_load(self):
"""This hook is run after all the torrents have been loaded into the
session from the saved state. It is called prior to resuming the
torrents and they all will have a 'Paused' state."""
log.debug("run_post_session_load")
for function in self.hooks["post_session_load"]:
function()
def get_torrent_list(self):
"""Returns a list of torrent_id's in the current session."""
return component.get("TorrentManager").get_torrent_list()
def block_ip_range(self, range):
"""Blocks the ip range in the core"""
return self.core.export_block_ip_range(range)
def reset_ip_filter(self):
"""Resets the ip filter"""
return self.core.export_reset_ip_filter()

View File

@ -1,476 +1,476 @@
#
# Copyright (C) 2008-2010 Andrew Resch <andrewresch@gmail.com>
# preferencesmanager.py
#
# 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.
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import logging
import os
import platform
import random
import os.path
import threading
from urllib.parse import quote_plus
from urllib.request import urlopen
import gobject
from twisted.internet.task import LoopingCall
try:
import deluge.libtorrent as lt
except ImportError:
import libtorrent as lt
if not (lt.version_major == 0 and lt.version_minor == 14):
raise ImportError("This version of Deluge requires libtorrent 0.14!")
import deluge.configmanager
import deluge.common
import deluge.component as component
import deluge.configmanager
from deluge._libtorrent import lt
from deluge.event import ConfigValueChangedEvent
GeoIP = None
try:
from GeoIP import GeoIP
except ImportError:
try:
from pygeoip import GeoIP
except ImportError:
pass
log = logging.getLogger(__name__)
from deluge.log import LOG as log
DEFAULT_PREFS = {
'send_info': False,
'info_sent': 0.0,
'daemon_port': 58846,
'allow_remote': False,
'pre_allocate_storage': False,
'download_location': deluge.common.get_default_download_dir(),
'listen_ports': [6881, 6891],
'listen_interface': '',
'outgoing_interface': '',
'random_port': True,
'listen_random_port': None,
'listen_use_sys_port': False,
'listen_reuse_port': True,
'outgoing_ports': [0, 0],
'random_outgoing_ports': True,
'copy_torrent_file': False,
'del_copy_torrent_file': False,
'torrentfiles_location': deluge.common.get_default_download_dir(),
'plugins_location': os.path.join(deluge.configmanager.get_config_dir(), 'plugins'),
'prioritize_first_last_pieces': False,
'sequential_download': False,
'dht': True,
'upnp': True,
'natpmp': True,
'utpex': True,
'lsd': True,
'enc_in_policy': 1,
'enc_out_policy': 1,
'enc_level': 2,
'max_connections_global': 200,
'max_upload_speed': -1.0,
'max_download_speed': -1.0,
'max_upload_slots_global': 4,
'max_half_open_connections': (
lambda: deluge.common.windows_check()
and (lambda: deluge.common.vista_check() and 4 or 8)()
or 50
)(),
'max_connections_per_second': 20,
'ignore_limits_on_local_network': True,
'max_connections_per_torrent': -1,
'max_upload_slots_per_torrent': -1,
'max_upload_speed_per_torrent': -1,
'max_download_speed_per_torrent': -1,
'enabled_plugins': [],
'add_paused': False,
'max_active_seeding': 5,
'max_active_downloading': 3,
'max_active_limit': 8,
'dont_count_slow_torrents': False,
'queue_new_to_top': False,
'stop_seed_at_ratio': False,
'remove_seed_at_ratio': False,
'stop_seed_ratio': 2.00,
'share_ratio_limit': 2.00,
'seed_time_ratio_limit': 7.00,
'seed_time_limit': 180,
'auto_managed': True,
'move_completed': False,
'move_completed_path': deluge.common.get_default_download_dir(),
'move_completed_paths_list': [],
'download_location_paths_list': [],
'path_chooser_show_chooser_button_on_localhost': True,
'path_chooser_auto_complete_enabled': True,
'path_chooser_accelerator_string': 'Tab',
'path_chooser_max_popup_rows': 20,
'path_chooser_show_hidden_files': False,
'new_release_check': True,
'proxy': {
'type': 0,
'hostname': '',
'username': '',
'password': '',
'port': 8080,
'proxy_hostnames': True,
'proxy_peer_connections': True,
'proxy_tracker_connections': True,
'force_proxy': False,
'anonymous_mode': False,
},
'peer_tos': '0x00',
'rate_limit_ip_overhead': True,
'geoip_db_location': '/usr/share/GeoIP/GeoIP.dat',
'cache_size': 512,
'cache_expiry': 60,
'auto_manage_prefer_seeds': False,
'shared': False,
'super_seeding': False,
}
"config_location": deluge.configmanager.get_config_dir(),
"send_info": False,
"info_sent": 0.0,
"daemon_port": 58846,
"allow_remote": False,
"compact_allocation": False,
"download_location": deluge.common.get_default_download_dir(),
"listen_ports": [6881, 6891],
"copy_torrent_file": False,
"torrentfiles_location": deluge.common.get_default_download_dir(),
"plugins_location": os.path.join(deluge.configmanager.get_config_dir(), "plugins"),
"state_location": os.path.join(deluge.configmanager.get_config_dir(), "state"),
"prioritize_first_last_pieces": False,
"random_port": True,
"dht": True,
"upnp": True,
"natpmp": True,
"utpex": True,
"lsd": True,
"enc_in_policy": 1,
"enc_out_policy": 1,
"enc_level": 2,
"enc_prefer_rc4": True,
"max_connections_global": 200,
"max_upload_speed": -1.0,
"max_download_speed": -1.0,
"max_upload_slots_global": 4,
"max_half_open_connections": (lambda: deluge.common.windows_check() and
(lambda: deluge.common.vista_check() and 4 or 8)() or 50)(),
"max_connections_per_second": 20,
"ignore_limits_on_local_network": True,
"max_connections_per_torrent": -1,
"max_upload_slots_per_torrent": -1,
"max_upload_speed_per_torrent": -1,
"max_download_speed_per_torrent": -1,
"enabled_plugins": [],
"autoadd_location": deluge.common.get_default_download_dir(),
"autoadd_enable": False,
"add_paused": False,
"max_active_seeding": 5,
"max_active_downloading": 3,
"max_active_limit": 8,
"dont_count_slow_torrents": False,
"queue_new_to_top": False,
"stop_seed_at_ratio": False,
"remove_seed_at_ratio": False,
"stop_seed_ratio": 2.00,
"share_ratio_limit": 2.00,
"seed_time_ratio_limit": 7.00,
"seed_time_limit": 180,
"auto_managed": True,
"move_completed": False,
"move_completed_path": deluge.common.get_default_download_dir(),
"new_release_check": True,
"proxies": {
"peer": {
"type": 0,
"hostname": "",
"username": "",
"password": "",
"port": 8080
},
"web_seed": {
"type": 0,
"hostname": "",
"username": "",
"password": "",
"port": 8080
},
"tracker": {
"type": 0,
"hostname": "",
"username": "",
"password": "",
"port": 8080
},
"dht": {
"type": 0,
"hostname": "",
"username": "",
"password": "",
"port": 8080
},
},
"outgoing_ports": [0, 0],
"random_outgoing_ports": True,
"peer_tos": "0x00",
"rate_limit_ip_overhead": True
}
class PreferencesManager(component.Component):
def __init__(self):
component.Component.__init__(self, 'PreferencesManager')
self.config = deluge.configmanager.ConfigManager('core.conf', DEFAULT_PREFS)
if 'proxies' in self.config:
log.warning(
'Updating config file for proxy, using "peer" values to fill new "proxy" setting'
)
self.config['proxy'].update(self.config['proxies']['peer'])
log.warning('New proxy config is: %s', self.config['proxy'])
del self.config['proxies']
if 'i2p_proxy' in self.config and self.config['i2p_proxy']['hostname']:
self.config['proxy'].update(self.config['i2p_proxy'])
self.config['proxy']['type'] = 6
del self.config['i2p_proxy']
if 'anonymous_mode' in self.config:
self.config['proxy']['anonymous_mode'] = self.config['anonymous_mode']
del self.config['anonymous_mode']
if 'proxy' in self.config:
for key in DEFAULT_PREFS['proxy']:
if key not in self.config['proxy']:
self.config['proxy'][key] = DEFAULT_PREFS['proxy'][key]
component.Component.__init__(self, "PreferencesManager")
self.core = component.get('Core')
self.new_release_timer = None
self.config = deluge.configmanager.ConfigManager("core.conf", DEFAULT_PREFS)
def start(self):
# Set the initial preferences on start-up
for key in DEFAULT_PREFS:
self.do_config_set_func(key, self.config[key])
self.core = component.get("Core")
self.session = component.get("Core").session
self.settings = component.get("Core").settings
self.signals = component.get("SignalManager")
# Register set functions in the Config
self.config.register_set_function("torrentfiles_location",
self._on_set_torrentfiles_location)
self.config.register_set_function("state_location",
self._on_set_state_location)
self.config.register_set_function("listen_ports",
self._on_set_listen_ports)
self.config.register_set_function("random_port",
self._on_set_random_port)
self.config.register_set_function("outgoing_ports",
self._on_set_outgoing_ports)
self.config.register_set_function("random_outgoing_ports",
self._on_set_random_outgoing_ports)
self.config.register_set_function("peer_tos",
self._on_set_peer_tos)
self.config.register_set_function("dht", self._on_set_dht)
self.config.register_set_function("upnp", self._on_set_upnp)
self.config.register_set_function("natpmp", self._on_set_natpmp)
self.config.register_set_function("utpex", self._on_set_utpex)
self.config.register_set_function("lsd", self._on_set_lsd)
self.config.register_set_function("enc_in_policy",
self._on_set_encryption)
self.config.register_set_function("enc_out_policy",
self._on_set_encryption)
self.config.register_set_function("enc_level",
self._on_set_encryption)
self.config.register_set_function("enc_prefer_rc4",
self._on_set_encryption)
self.config.register_set_function("max_connections_global",
self._on_set_max_connections_global)
self.config.register_set_function("max_upload_speed",
self._on_set_max_upload_speed)
self.config.register_set_function("max_download_speed",
self._on_set_max_download_speed)
self.config.register_set_function("max_upload_slots_global",
self._on_set_max_upload_slots_global)
self.config.register_set_function("max_half_open_connections",
self._on_set_max_half_open_connections)
self.config.register_set_function("max_connections_per_second",
self._on_set_max_connections_per_second)
self.config.register_set_function("ignore_limits_on_local_network",
self._on_ignore_limits_on_local_network)
self.config.register_set_function("share_ratio_limit",
self._on_set_share_ratio_limit)
self.config.register_set_function("seed_time_ratio_limit",
self._on_set_seed_time_ratio_limit)
self.config.register_set_function("seed_time_limit",
self._on_set_seed_time_limit)
self.config.register_set_function("max_active_downloading",
self._on_set_max_active_downloading)
self.config.register_set_function("max_active_seeding",
self._on_set_max_active_seeding)
self.config.register_set_function("max_active_limit",
self._on_set_max_active_limit)
self.config.register_set_function("dont_count_slow_torrents",
self._on_set_dont_count_slow_torrents)
self.config.register_set_function("send_info",
self._on_send_info)
self.config.register_set_function("proxies",
self._on_set_proxies)
self.new_release_timer = None
self.config.register_set_function("new_release_check",
self._on_new_release_check)
self.config.register_set_function("rate_limit_ip_overhead",
self._on_rate_limit_ip_overhead)
self.config.register_change_callback(self._on_config_value_change)
def stop(self):
if self.new_release_timer and self.new_release_timer.running:
self.new_release_timer.stop()
# Config set functions
def do_config_set_func(self, key, value):
on_set_func = getattr(self, '_on_set_' + key, None)
if on_set_func:
if log.isEnabledFor(logging.DEBUG):
log.debug('Config key: %s set to %s..', key, value)
on_set_func(key, value)
def _on_config_value_change(self, key, value):
if self.get_state() == 'Started':
self.do_config_set_func(key, value)
component.get('EventManager').emit(ConfigValueChangedEvent(key, value))
self.signals.emit("config_value_changed", key, value)
def _on_set_torrentfiles_location(self, key, value):
if self.config['copy_torrent_file']:
if self.config["copy_torrent_file"]:
try:
os.makedirs(value)
except OSError as ex:
log.debug('Unable to make directory: %s', ex)
except Exception, e:
log.debug("Unable to make directory: %s", e)
def _on_set_state_location(self, key, value):
if not os.access(value, os.F_OK):
try:
os.makedirs(value)
except Exception, e:
log.debug("Unable to make directory: %s", e)
def _on_set_listen_ports(self, key, value):
self.__set_listen_on()
def _on_set_listen_interface(self, key, value):
self.__set_listen_on()
def _on_set_outgoing_interface(self, key, value):
"""Set interface name or IP address for outgoing BitTorrent connections."""
value = value.strip() if value else ''
self.core.apply_session_settings({'outgoing_interfaces': value})
# Only set the listen ports if random_port is not true
if self.config["random_port"] is not True:
log.debug("listen port range set to %s-%s", value[0], value[1])
self.session.listen_on(value[0], value[1])
def _on_set_random_port(self, key, value):
self.__set_listen_on()
def __set_listen_on(self):
"""Set the ports and interface address to listen for incoming connections on."""
if self.config['random_port']:
if not self.config['listen_random_port']:
self.config['listen_random_port'] = random.randrange(49152, 65525)
listen_ports = [
self.config['listen_random_port']
] * 2 # use single port range
log.debug("random port value set to %s", value)
# We need to check if the value has been changed to true and false
# and then handle accordingly.
if value:
import random
listen_ports = []
randrange = lambda: random.randrange(49152, 65525)
listen_ports.append(randrange())
listen_ports.append(listen_ports[0]+10)
else:
self.config['listen_random_port'] = None
listen_ports = self.config['listen_ports']
listen_ports = self.config["listen_ports"]
if self.config['listen_interface']:
interface = self.config['listen_interface'].strip()
else:
interface = '0.0.0.0'
log.debug(
'Listen Interface: %s, Ports: %s with use_sys_port: %s',
interface,
listen_ports,
self.config['listen_use_sys_port'],
)
interfaces = [
f'{interface}:{port}'
for port in range(listen_ports[0], listen_ports[1] + 1)
]
self.core.apply_session_settings(
{
'listen_system_port_fallback': self.config['listen_use_sys_port'],
'listen_interfaces': ','.join(interfaces),
}
)
# Set the listen ports
log.debug("listen port range set to %s-%s", listen_ports[0],
listen_ports[1])
self.session.listen_on(listen_ports[0], listen_ports[1])
def _on_set_outgoing_ports(self, key, value):
self.__set_outgoing_ports()
if not self.config["random_outgoing_ports"]:
log.debug("outgoing port range set to %s-%s", value[0], value[1])
self.settings.outgoing_ports = value[0], value[1]
self.session.set_settings(self.settings)
def _on_set_random_outgoing_ports(self, key, value):
self.__set_outgoing_ports()
def __set_outgoing_ports(self):
port = (
0
if self.config['random_outgoing_ports']
else self.config['outgoing_ports'][0]
)
if port:
num_ports = (
self.config['outgoing_ports'][1] - self.config['outgoing_ports'][0]
)
num_ports = num_ports if num_ports > 1 else 5
else:
num_ports = 0
log.debug('Outgoing port set to %s with range: %s', port, num_ports)
self.core.apply_session_settings(
{'outgoing_port': port, 'num_outgoing_ports': num_ports}
)
if value:
self.session.outgoing_ports(0, 0)
def _on_set_peer_tos(self, key, value):
log.debug("setting peer_tos to: %s", value)
try:
self.core.apply_session_setting('peer_tos', int(value, 16))
except ValueError as ex:
log.error('Invalid tos byte: %s', ex)
self.settings.peer_tos = chr(int(value, 16))
except ValueError, e:
log.debug("Invalid tos byte: %s", e)
return
self.session.set_settings(self.settings)
def _on_set_dht(self, key, value):
lt_bootstraps = self.core.session.get_settings()['dht_bootstrap_nodes']
# Update list of lt bootstraps, using set to remove duplicates.
dht_bootstraps = set(
lt_bootstraps.split(',')
+ [
'router.bittorrent.com:6881',
'router.utorrent.com:6881',
'router.bitcomet.com:6881',
'dht.transmissionbt.com:6881',
'dht.aelitis.com:6881',
]
)
self.core.apply_session_settings(
{'dht_bootstrap_nodes': ','.join(dht_bootstraps), 'enable_dht': value}
)
log.debug("dht value set to %s", value)
state_file = deluge.common.get_default_config_dir("dht.state")
if value:
state = None
try:
state = lt.bdecode(open(state_file, "rb").read())
except Exception, e:
log.warning("Unable to read DHT state file: %s", e)
try:
self.session.start_dht(state)
except Exception, e:
log.warning("Restoring old DHT state failed: %s", e)
self.session.start_dht(None)
self.session.add_dht_router("router.bittorrent.com", 6881)
self.session.add_dht_router("router.utorrent.com", 6881)
self.session.add_dht_router("router.bitcomet.com", 6881)
else:
self.core.save_dht_state()
self.session.stop_dht()
def _on_set_upnp(self, key, value):
self.core.apply_session_setting('enable_upnp', value)
log.debug("upnp value set to %s", value)
if value:
self.session.start_upnp()
else:
self.session.stop_upnp()
def _on_set_natpmp(self, key, value):
self.core.apply_session_setting('enable_natpmp', value)
log.debug("natpmp value set to %s", value)
if value:
self.session.start_natpmp()
else:
self.session.stop_natpmp()
def _on_set_lsd(self, key, value):
self.core.apply_session_setting('enable_lsd', value)
log.debug("lsd value set to %s", value)
if value:
self.session.start_lsd()
else:
self.session.stop_lsd()
def _on_set_utpex(self, key, value):
log.debug("utpex value set to %s", value)
if value:
self.core.session.add_extension('ut_pex')
def _on_set_enc_in_policy(self, key, value):
self._on_set_encryption(key, value)
def _on_set_enc_out_policy(self, key, value):
self._on_set_encryption(key, value)
def _on_set_enc_level(self, key, value):
self._on_set_encryption(key, value)
self.session.add_extension(lt.create_ut_pex_plugin)
def _on_set_encryption(self, key, value):
# Convert Deluge enc_level values to libtorrent enc_level values.
pe_enc_level = {
0: lt.enc_level.plaintext,
1: lt.enc_level.rc4,
2: lt.enc_level.both,
}
self.core.apply_session_settings(
{
'out_enc_policy': lt.enc_policy(self.config['enc_out_policy']),
'in_enc_policy': lt.enc_policy(self.config['enc_in_policy']),
'allowed_enc_level': lt.enc_level(
pe_enc_level[self.config['enc_level']]
),
'prefer_rc4': True,
}
)
log.debug("encryption value %s set to %s..", key, value)
pe_settings = lt.pe_settings()
pe_settings.out_enc_policy = \
lt.enc_policy(self.config["enc_out_policy"])
pe_settings.in_enc_policy = lt.enc_policy(self.config["enc_in_policy"])
pe_settings.allowed_enc_level = lt.enc_level(self.config["enc_level"])
pe_settings.prefer_rc4 = self.config["enc_prefer_rc4"]
self.session.set_pe_settings(pe_settings)
set = self.session.get_pe_settings()
log.debug("encryption settings:\n\t\t\tout_policy: %s\n\t\t\
in_policy: %s\n\t\t\tlevel: %s\n\t\t\tprefer_rc4: %s",
set.out_enc_policy,
set.in_enc_policy,
set.allowed_enc_level,
set.prefer_rc4)
def _on_set_max_connections_global(self, key, value):
self.core.apply_session_setting('connections_limit', value)
log.debug("max_connections_global set to %s..", value)
self.session.set_max_connections(value)
def _on_set_max_upload_speed(self, key, value):
log.debug("max_upload_speed set to %s..", value)
# We need to convert Kb/s to B/s
value = -1 if value < 0 else int(value * 1024)
self.core.apply_session_setting('upload_rate_limit', value)
if value < 0:
v = -1
else:
v = int(value * 1024)
self.session.set_upload_rate_limit(v)
def _on_set_max_download_speed(self, key, value):
log.debug("max_download_speed set to %s..", value)
# We need to convert Kb/s to B/s
value = -1 if value < 0 else int(value * 1024)
self.core.apply_session_setting('download_rate_limit', value)
if value < 0:
v = -1
else:
v = int(value * 1024)
self.session.set_download_rate_limit(v)
def _on_set_max_upload_slots_global(self, key, value):
self.core.apply_session_setting('unchoke_slots_limit', value)
log.debug("max_upload_slots_global set to %s..", value)
self.session.set_max_uploads(value)
def _on_set_max_half_open_connections(self, key, value):
self.core.apply_session_setting('half_open_limit', value)
self.session.set_max_half_open_connections(value)
def _on_set_max_connections_per_second(self, key, value):
self.core.apply_session_setting('connection_speed', value)
self.settings.connection_speed = value
self.session.set_settings(self.settings)
def _on_set_ignore_limits_on_local_network(self, key, value):
self.core.apply_session_setting('ignore_limits_on_local_network', value)
def _on_ignore_limits_on_local_network(self, key, value):
self.settings.ignore_limits_on_local_network = value
self.session.set_settings(self.settings)
def _on_set_share_ratio_limit(self, key, value):
# This value is a float percentage in deluge, but libtorrent needs int percentage.
self.core.apply_session_setting('share_ratio_limit', int(value * 100))
log.debug("%s set to %s..", key, value)
self.settings.share_ratio_limit = value
self.session.set_settings(self.settings)
def _on_set_seed_time_ratio_limit(self, key, value):
# This value is a float percentage in deluge, but libtorrent needs int percentage.
self.core.apply_session_setting('seed_time_ratio_limit', int(value * 100))
log.debug("%s set to %s..", key, value)
self.settings.seed_time_ratio_limit = value
self.session.set_settings(self.settings)
def _on_set_seed_time_limit(self, key, value):
log.debug("%s set to %s..", key, value)
# This value is stored in minutes in deluge, but libtorrent wants seconds
self.core.apply_session_setting('seed_time_limit', int(value * 60))
self.settings.seed_time_limit = int(value * 60)
self.session.set_settings(self.settings)
def _on_set_max_active_downloading(self, key, value):
self.core.apply_session_setting('active_downloads', value)
log.debug("%s set to %s..", key, value)
log.debug("active_downloads: %s", self.settings.active_downloads)
self.settings.active_downloads = value
self.session.set_settings(self.settings)
def _on_set_max_active_seeding(self, key, value):
self.core.apply_session_setting('active_seeds', value)
log.debug("%s set to %s..", key, value)
log.debug("active_seeds: %s", self.settings.active_seeds)
self.settings.active_seeds = value
self.session.set_settings(self.settings)
def _on_set_max_active_limit(self, key, value):
self.core.apply_session_setting('active_limit', value)
log.debug("%s set to %s..", key, value)
log.debug("active_limit: %s", self.settings.active_limit)
self.settings.active_limit = value
self.session.set_settings(self.settings)
def _on_set_dont_count_slow_torrents(self, key, value):
self.core.apply_session_setting('dont_count_slow_torrents', value)
log.debug("%s set to %s..", key, value)
self.settings.dont_count_slow_torrents = value
self.session.set_settings(self.settings)
def _on_set_send_info(self, key, value):
def _on_send_info(self, key, value):
log.debug("Sending anonymous stats..")
"""sends anonymous stats home"""
log.debug('Sending anonymous stats..')
class SendInfoThread(threading.Thread):
class Send_Info_Thread(threading.Thread):
def __init__(self, config):
self.config = config
threading.Thread.__init__(self)
def run(self):
import time
now = time.time()
# check if we've done this within the last week or never
if (now - self.config['info_sent']) >= (60 * 60 * 24 * 7):
if (now - self.config["info_sent"]) >= (60 * 60 * 24 * 7):
import deluge.common
from urllib import quote_plus
from urllib2 import urlopen
import platform
try:
url = (
'http://deluge-torrent.org/stats_get.php?processor='
+ platform.machine()
+ '&python='
+ platform.python_version()
+ '&deluge='
+ deluge.common.get_version()
+ '&os='
+ platform.system()
+ '&plugins='
+ quote_plus(':'.join(self.config['enabled_plugins']))
)
url = "http://deluge-torrent.org/stats_get.php?processor=" + \
platform.machine() + "&python=" + platform.python_version() \
+ "&deluge=" + deluge.common.get_version() \
+ "&os=" + platform.system() \
+ "&plugins=" + quote_plus(self.config["enabled_plugins"])
urlopen(url)
except OSError as ex:
log.debug('Network error while trying to send info: %s', ex)
except IOError, e:
log.debug("Network error while trying to send info: %s", e)
else:
self.config['info_sent'] = now
self.config["info_sent"] = now
if value:
SendInfoThread(self.config).start()
Send_Info_Thread(self.config).start()
def _on_set_new_release_check(self, key, value):
def _on_new_release_check(self, key, value):
if value:
log.debug('Checking for new release..')
log.debug("Checking for new release..")
threading.Thread(target=self.core.get_new_release).start()
if self.new_release_timer and self.new_release_timer.running:
self.new_release_timer.stop()
if self.new_release_timer:
gobject.source_remove(self.new_release_timer)
# Set a timer to check for a new release every 3 days
self.new_release_timer = LoopingCall(
self._on_set_new_release_check, 'new_release_check', True
)
self.new_release_timer.start(72 * 60 * 60, False)
self.new_release_timer = gobject.timeout_add(
72 * 60 * 60 * 1000, self._on_new_release_check, "new_release_check", True)
else:
if self.new_release_timer and self.new_release_timer.running:
self.new_release_timer.stop()
if self.new_release_timer:
gobject.source.remove(self.new_release_timer)
def _on_set_proxy(self, key, value):
# Initialise with type none and blank hostnames.
proxy_settings = {
'proxy_type': lt.proxy_type_t.none,
'i2p_hostname': '',
'proxy_hostname': '',
'proxy_hostnames': value['proxy_hostnames'],
'proxy_peer_connections': value['proxy_peer_connections'],
'proxy_tracker_connections': value['proxy_tracker_connections'],
'force_proxy': value['force_proxy'],
'anonymous_mode': value['anonymous_mode'],
}
def _on_set_proxies(self, key, value):
for k, v in value.items():
if v["type"]:
proxy_settings = lt.proxy_settings()
proxy_settings.type = lt.proxy_type(v["type"])
proxy_settings.username = v["username"]
proxy_settings.password = v["password"]
proxy_settings.hostname = v["hostname"]
proxy_settings.port = v["port"]
log.debug("setting %s proxy settings", k)
getattr(self.session, "set_%s_proxy" % k)(proxy_settings)
if value['type'] == lt.proxy_type_t.i2p_proxy:
proxy_settings.update(
{
'proxy_type': lt.proxy_type_t.i2p_proxy,
'i2p_hostname': value['hostname'],
'i2p_port': value['port'],
}
)
elif value['type'] != lt.proxy_type_t.none:
proxy_settings.update(
{
'proxy_type': value['type'],
'proxy_hostname': value['hostname'],
'proxy_port': value['port'],
'proxy_username': value['username'],
'proxy_password': value['password'],
}
)
self.core.apply_session_settings(proxy_settings)
def _on_set_rate_limit_ip_overhead(self, key, value):
self.core.apply_session_setting('rate_limit_ip_overhead', value)
def _on_set_geoip_db_location(self, key, geoipdb_path):
# Load the GeoIP DB for country look-ups if available
if os.path.exists(geoipdb_path):
try:
self.core.geoip_instance = GeoIP(geoipdb_path, 0)
except Exception as ex:
log.warning('GeoIP Unavailable: %s', ex)
else:
log.warning('Unable to find GeoIP database file: %s', geoipdb_path)
def _on_set_cache_size(self, key, value):
self.core.apply_session_setting('cache_size', value)
def _on_set_cache_expiry(self, key, value):
self.core.apply_session_setting('cache_expiry', value)
def _on_auto_manage_prefer_seeds(self, key, value):
self.core.apply_session_setting('auto_manage_prefer_seeds', value)
def _on_rate_limit_ip_overhead(self, key, value):
log.debug("%s: %s", key, value)
self.settings.rate_limit_ip_overhead = value
self.session.set_settings(self.settings)

View File

@ -1,598 +1,114 @@
#
# Copyright (C) 2008,2009 Andrew Resch <andrewresch@gmail.com>
# rpcserver.py
#
# 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.
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
"""RPCServer Module"""
import logging
import os
import sys
import traceback
from collections import namedtuple
from types import FunctionType
from typing import Callable, TypeVar, overload
from twisted.internet import defer, reactor
from twisted.internet.protocol import Factory, connectionDone
import gobject
from deluge.SimpleXMLRPCServer import SimpleXMLRPCServer
from deluge.SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
from SocketServer import ThreadingMixIn
from base64 import decodestring, encodestring
from deluge.log import LOG as log
import deluge.component as component
import deluge.configmanager
from deluge.core.authmanager import (
AUTH_LEVEL_ADMIN,
AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_NONE,
)
from deluge.crypto_utils import check_ssl_keys, get_context_factory
from deluge.error import (
DelugeError,
IncompatibleClient,
NotAuthorizedError,
WrappedException,
_ClientSideRecreateError,
)
from deluge.event import ClientDisconnectedEvent
from deluge.transfer import DelugeTransferProtocol
RPC_RESPONSE = 1
RPC_ERROR = 2
RPC_EVENT = 3
log = logging.getLogger(__name__)
TCallable = TypeVar('TCallable', bound=Callable)
@overload
def export(func: TCallable) -> TCallable:
...
@overload
def export(auth_level: int) -> Callable[[TCallable], TCallable]:
...
def export(auth_level=AUTH_LEVEL_DEFAULT):
"""
Decorator function to register an object's method as an RPC. The object
will need to be registered with an :class:`RPCServer` to be effective.
:param func: the function to export
:type func: function
:param auth_level: the auth level required to call this method
:type auth_level: int
"""
def wrap(func, *args, **kwargs):
func._rpcserver_export = True
func._rpcserver_auth_level = auth_level
rpc_text = '**RPC exported method** (*Auth level: %s*)' % auth_level
# Append the RPC text while ensuring correct docstring formatting.
if func.__doc__:
if func.__doc__.endswith(' '):
indent = func.__doc__.split('\n')[-1]
func.__doc__ += f'\n{indent}'
else:
func.__doc__ += '\n\n'
func.__doc__ += rpc_text
else:
func.__doc__ = rpc_text
return func
if isinstance(auth_level, FunctionType):
func = auth_level
auth_level = AUTH_LEVEL_DEFAULT
return wrap(func)
else:
return wrap
def format_request(call):
"""
Format the RPCRequest message for debug printing
:param call: the request
:type call: a RPCRequest
:returns: a formatted string for printing
:rtype: str
"""
try:
s = call[1] + '('
if call[2]:
s += ', '.join([str(x) for x in call[2]])
if call[3]:
if call[2]:
s += ', '
s += ', '.join([key + '=' + str(value) for key, value in call[3].items()])
s += ')'
except UnicodeEncodeError:
return 'UnicodeEncodeError, call: %s' % call
else:
return s
class DelugeRPCProtocol(DelugeTransferProtocol):
def __init__(self):
super().__init__()
# namedtuple subclass with auth_level, username for the connected session.
self.AuthLevel = namedtuple('SessionAuthlevel', 'auth_level, username')
def message_received(self, request):
"""
This method is called whenever a message is received from a client. The
only message that a client sends to the server is a RPC Request message.
If the RPC Request message is valid, then the method is called in
:meth:`dispatch`.
:param request: the request from the client.
:type data: tuple
"""
if not isinstance(request, tuple):
log.debug('Received invalid message: type is not tuple')
return
if len(request) < 1:
log.debug('Received invalid message: there are no items')
return
for call in request:
if len(call) != 4:
log.debug(
'Received invalid rpc request: number of items ' 'in request is %s',
len(call),
)
continue
# log.debug('RPCRequest: %s', format_request(call))
reactor.callLater(0, self.dispatch, *call)
def sendData(self, data): # NOQA: N802
"""
Sends the data to the client.
:param data: the object that is to be sent to the client. This should
be one of the RPC message types.
:type data: object
"""
try:
self.transfer_message(data)
except Exception as ex:
log.warning('Error occurred when sending message: %s.', ex)
log.exception(ex)
raise
def connectionMade(self): # NOQA: N802
"""
This method is called when a new client connects.
"""
peer = self.transport.getPeer()
log.info('Deluge Client connection made from: %s:%s', peer.host, peer.port)
# Set the initial auth level of this session to AUTH_LEVEL_NONE
self.factory.authorized_sessions[self.transport.sessionno] = self.AuthLevel(
AUTH_LEVEL_NONE, ''
)
def connectionLost(self, reason=connectionDone): # NOQA: N802
"""
This method is called when the client is disconnected.
:param reason: the reason the client disconnected.
:type reason: str
"""
# We need to remove this session from various dicts
del self.factory.authorized_sessions[self.transport.sessionno]
if self.transport.sessionno in self.factory.session_protocols:
del self.factory.session_protocols[self.transport.sessionno]
if self.transport.sessionno in self.factory.interested_events:
del self.factory.interested_events[self.transport.sessionno]
if self.factory.state == 'running':
component.get('EventManager').emit(
ClientDisconnectedEvent(self.factory.session_id)
)
log.info('Deluge client disconnected: %s', reason.value)
def valid_session(self):
return self.transport.sessionno in self.factory.authorized_sessions
def dispatch(self, request_id, method, args, kwargs):
"""
This method is run when a RPC Request is made. It will run the local method
and will send either a RPC Response or RPC Error back to the client.
:param request_id: the request_id from the client (sent in the RPC Request)
:type request_id: int
:param method: the local method to call. It must be registered with
the :class:`RPCServer`.
:type method: str
:param args: the arguments to pass to `method`
:type args: list
:param kwargs: the keyword-arguments to pass to `method`
:type kwargs: dict
"""
def send_error():
"""
Sends an error response with the contents of the exception that was raised.
"""
exc_type, exc_value, dummy_exc_trace = sys.exc_info()
formated_tb = traceback.format_exc()
try:
self.sendData(
(
RPC_ERROR,
request_id,
exc_type.__name__,
exc_value._args,
exc_value._kwargs,
formated_tb,
)
)
except AttributeError:
# This is not a deluge exception (object has no attribute '_args), let's wrap it
log.warning(
'An exception occurred while sending RPC_ERROR to '
'client. Wrapping it and resending. Error to '
'send(causing exception goes next):\n%s',
formated_tb,
)
try:
raise WrappedException(
str(exc_value), exc_type.__name__, formated_tb
)
except WrappedException:
send_error()
except Exception as ex:
log.error(
'An exception occurred while sending RPC_ERROR to client: %s', ex
)
if method == 'daemon.info':
# This is a special case and used in the initial connection process
self.sendData((RPC_RESPONSE, request_id, deluge.common.get_version()))
return
elif method == 'daemon.login':
# This is a special case and used in the initial connection process
# We need to authenticate the user here
log.debug('RPC dispatch daemon.login')
try:
client_version = kwargs.pop('client_version', None)
if client_version is None:
raise IncompatibleClient(deluge.common.get_version())
ret = component.get('AuthManager').authorize(*args, **kwargs)
if ret:
self.factory.authorized_sessions[
self.transport.sessionno
] = self.AuthLevel(ret, args[0])
self.factory.session_protocols[self.transport.sessionno] = self
except Exception as ex:
send_error()
if not isinstance(ex, _ClientSideRecreateError):
log.exception(ex)
else:
self.sendData((RPC_RESPONSE, request_id, (ret)))
if not ret:
self.transport.loseConnection()
return
# Anything below requires a valid session
if not self.valid_session():
return
if method == 'daemon.set_event_interest':
log.debug('RPC dispatch daemon.set_event_interest')
# This special case is to allow clients to set which events they are
# interested in receiving.
# We are expecting a sequence from the client.
try:
if self.transport.sessionno not in self.factory.interested_events:
self.factory.interested_events[self.transport.sessionno] = []
self.factory.interested_events[self.transport.sessionno].extend(args[0])
except Exception:
send_error()
else:
self.sendData((RPC_RESPONSE, request_id, (True)))
return
if method not in self.factory.methods:
try:
# Raise exception to be sent back to client
raise AttributeError('RPC call on invalid function: %s' % method)
except AttributeError:
send_error()
return
log.debug('RPC dispatch %s', method)
try:
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
auth_level = self.factory.authorized_sessions[
self.transport.sessionno
].auth_level
if auth_level < method_auth_requirement:
# This session is not allowed to call this method
log.debug(
'Session %s is attempting an unauthorized method call!',
self.transport.sessionno,
)
raise NotAuthorizedError(auth_level, method_auth_requirement)
# Set the session_id in the factory so that methods can know
# which session is calling it.
self.factory.session_id = self.transport.sessionno
ret = self.factory.methods[method](*args, **kwargs)
except Exception as ex:
send_error()
# Don't bother printing out DelugeErrors, because they are just
# for the client
if not isinstance(ex, DelugeError):
log.exception('Exception calling RPC request: %s', ex)
else:
# Check if the return value is a deferred, since we'll need to
# wait for it to fire before sending the RPC_RESPONSE
if isinstance(ret, defer.Deferred):
def on_success(result):
try:
self.sendData((RPC_RESPONSE, request_id, result))
except Exception:
send_error()
return result
def on_fail(failure):
try:
failure.raiseException()
except Exception:
send_error()
return failure
ret.addCallbacks(on_success, on_fail)
else:
self.sendData((RPC_RESPONSE, request_id, ret))
def export(func):
func._rpcserver_export = True
return func
class RPCServer(component.Component):
"""
This class is used to handle rpc requests from the client. Objects are
registered with this class and their methods are exported using the export
decorator.
def __init__(self, port):
component.Component.__init__(self, "RPCServer")
:param port: the port the RPCServer will listen on
:type port: int
:param interface: the interface to listen on, this may override the `allow_remote` setting
:type interface: str
:param allow_remote: set True if the server should allow remote connections
:type allow_remote: bool
:param listen: if False, will not start listening.. This is only useful in Classic Mode
:type listen: bool
"""
# Get config
self.config = deluge.configmanager.ConfigManager("core.conf")
def __init__(self, port=58846, interface='', allow_remote=False, listen=True):
component.Component.__init__(self, 'RPCServer')
if port == None:
port = self.config["daemon_port"]
self.factory = Factory()
self.factory.protocol = DelugeRPCProtocol
self.factory.session_id = -1
self.factory.state = 'running'
# Holds the registered methods
self.factory.methods = {}
# Holds the session_ids and auth levels
self.factory.authorized_sessions = {}
# Holds the protocol objects with the session_id as key
self.factory.session_protocols = {}
# Holds the interested event list for the sessions
self.factory.interested_events = {}
self.listen = listen
if not listen:
return
if allow_remote:
hostname = ''
if self.config["allow_remote"]:
hostname = ""
else:
hostname = 'localhost'
if interface:
hostname = interface
log.info('Starting DelugeRPC server %s:%s', hostname, port)
# Check for SSL keys and generate some if needed
check_ssl_keys()
cert = os.path.join(deluge.configmanager.get_config_dir('ssl'), 'daemon.cert')
pkey = os.path.join(deluge.configmanager.get_config_dir('ssl'), 'daemon.pkey')
hostname = "localhost"
# Setup the xmlrpc server
try:
reactor.listenSSL(
port, self.factory, get_context_factory(cert, pkey), interface=hostname
)
except Exception as ex:
log.debug('Daemon already running or port not available.: %s', ex)
raise
log.info("Starting XMLRPC server %s:%s", hostname, port)
self.server = XMLRPCServer((hostname, port),
requestHandler=BasicAuthXMLRPCRequestHandler,
logRequests=False,
allow_none=True)
except Exception, e:
log.info("Daemon already running or port not available..")
log.error(e)
sys.exit(0)
self.server.register_multicall_functions()
self.server.register_introspection_functions()
self.server.socket.setblocking(False)
gobject.io_add_watch(self.server.socket.fileno(), gobject.IO_IN | gobject.IO_OUT | gobject.IO_PRI | gobject.IO_ERR | gobject.IO_HUP, self._on_socket_activity)
def _on_socket_activity(self, source, condition):
"""This gets called when there is activity on the socket, ie, data to read
or to write."""
self.server.handle_request()
return True
def register_object(self, obj, name=None):
"""
Registers an object to export it's rpc methods. These methods should
be exported with the export decorator prior to registering the object.
:param obj: the object that we want to export
:type obj: object
:param name: the name to use, if None, it will be the class name of the object
:type name: str
"""
if not name:
name = obj.__class__.__name__.lower()
name = obj.__class__.__name__
for d in dir(obj):
if d[0] == '_':
if d[0] == "_":
continue
if getattr(getattr(obj, d), '_rpcserver_export', False):
log.debug('Registering method: %s', name + '.' + d)
self.factory.methods[name + '.' + d] = getattr(obj, d)
log.debug("Registering method: %s", name + "." + d)
self.server.register_function(getattr(obj, d), name + "." + d)
def deregister_object(self, obj):
class XMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
def get_request(self):
"""Get the request and client address from the socket.
We override this so that we can get the ip address of the client.
"""
Deregisters an objects exported rpc methods.
request, client_address = self.socket.accept()
self.client_address = client_address[0]
return (request, client_address)
class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
def do_POST(self):
if "authorization" in self.headers:
auth = self.headers['authorization']
auth = auth.replace("Basic ","")
decoded_auth = decodestring(auth)
# Check authentication here
if component.get("AuthManager").authorize(*decoded_auth.split(":")):
# User authorized, call the real do_POST now
return SimpleXMLRPCRequestHandler.do_POST(self)
:param obj: the object that was previously registered
"""
for key, value in self.factory.methods.items():
if value.__self__ == obj:
del self.factory.methods[key]
def get_object_method(self, name):
"""
Returns a registered method.
:param name: the name of the method, usually in the form of 'object.method'
:type name: str
:returns: method
:raises KeyError: if `name` is not registered
"""
return self.factory.methods[name]
def get_method_list(self):
"""
Returns a list of the exported methods.
:returns: the exported methods
:rtype: list
"""
return list(self.factory.methods)
def get_session_id(self):
"""
Returns the session id of the current RPC.
:returns: the session id, this will be -1 if no connections have been made
:rtype: int
"""
return self.factory.session_id
def get_session_user(self):
"""
Returns the username calling the current RPC.
:returns: the username of the user calling the current RPC
:rtype: string
"""
if not self.listen:
return 'localclient'
session_id = self.get_session_id()
if session_id > -1 and session_id in self.factory.authorized_sessions:
return self.factory.authorized_sessions[session_id].username
else:
# No connections made yet
return ''
def get_session_auth_level(self):
"""
Returns the auth level of the user calling the current RPC.
:returns: the auth level
:rtype: int
"""
if not self.listen or not self.is_session_valid(self.get_session_id()):
return AUTH_LEVEL_ADMIN
return self.factory.authorized_sessions[self.get_session_id()].auth_level
def get_rpc_auth_level(self, rpc):
"""
Returns the auth level requirement for an exported rpc.
:returns: the auth level
:rtype: int
"""
return self.factory.methods[rpc]._rpcserver_auth_level
def is_session_valid(self, session_id):
"""
Checks if the session is still valid, eg, if the client is still connected.
:param session_id: the session id
:type session_id: int
:returns: True if the session is valid
:rtype: bool
"""
return session_id in self.factory.authorized_sessions
def emit_event(self, event):
"""
Emits the event to interested clients.
:param event: the event to emit
:type event: :class:`deluge.event.DelugeEvent`
"""
log.debug('intevents: %s', self.factory.interested_events)
# Find sessions interested in this event
for session_id, interest in self.factory.interested_events.items():
if event.name in interest:
log.debug('Emit Event: %s %s', event.name, event.args)
# This session is interested so send a RPC_EVENT
self.factory.session_protocols[session_id].sendData(
(RPC_EVENT, event.name, event.args)
)
def emit_event_for_session_id(self, session_id, event):
"""
Emits the event to specified session_id.
:param session_id: the event to emit
:type session_id: int
:param event: the event to emit
:type event: :class:`deluge.event.DelugeEvent`
"""
if not self.is_session_valid(session_id):
log.debug(
'Session ID %s is not valid. Not sending event "%s".',
session_id,
event.name,
)
return
if session_id not in self.factory.interested_events:
log.debug(
'Session ID %s is not interested in any events. Not sending event "%s".',
session_id,
event.name,
)
return
if event.name not in self.factory.interested_events[session_id]:
log.debug(
'Session ID %s is not interested in event "%s". Not sending it.',
session_id,
event.name,
)
return
log.debug(
'Sending event "%s" with args "%s" to session id "%s".',
event.name,
event.args,
session_id,
)
self.factory.session_protocols[session_id].sendData(
(RPC_EVENT, event.name, event.args)
)
def stop(self):
self.factory.state = 'stopping'
# if cannot authenticate, end the connection
self.send_response(401)
self.end_headers()

View File

@ -0,0 +1,108 @@
#
# signalmanager.py
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import deluge.xmlrpclib as xmlrpclib
import socket
import struct
import gobject
import deluge.component as component
from deluge.log import LOG as log
class Transport(xmlrpclib.Transport):
def make_connection(self, host):
# create a HTTP connection object from a host descriptor
import httplib
host, extra_headers, x509 = self.get_host_info(host)
h = httplib.HTTP(host)
h._conn.connect()
h._conn.sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
struct.pack('ii', 1, 0))
return h
class SignalManager(component.Component):
def __init__(self):
component.Component.__init__(self, "SignalManager")
self.clients = {}
self.handlers = {}
def shutdown(self):
self.clients = {}
self.handlers = {}
def register_handler(self, signal, handler):
"""Registers a handler for signals"""
if signal not in self.handler.keys():
self.handler[signal] = []
self.handler[signal].append(handler)
log.debug("Registered signal handler for %s", signal)
def deregister_handler(self, handler):
"""De-registers the 'handler' function from all signal types."""
# Iterate through all handlers and remove 'handler' where found
for (key, value) in self.handlers:
if handler in value:
value.remove(handler)
def deregister_client(self, address):
"""Deregisters a client"""
log.debug("Deregistering %s as a signal reciever..", address)
for client in self.clients.keys():
if client.split("//")[1].split(":")[0] == address:
del self.clients[client]
break
def register_client(self, address, port):
"""Registers a client to emit signals to."""
uri = "http://" + str(address) + ":" + str(port)
log.debug("Registering %s as a signal reciever..", uri)
self.clients[uri] = xmlrpclib.ServerProxy(uri, transport=Transport())
#self.clients[uri].socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
# struct.pack('ii', 1, 0))
def emit(self, signal, *data):
# Run the handlers
if signal in self.handlers.keys():
for handler in self.handlers[signal]:
handler(*data)
for uri in self.clients:
gobject.idle_add(self._emit, uri, signal, 1, *data)
def _emit(self, uri, signal, count, *data):
if uri not in self.clients:
return
client = self.clients[uri]
try:
client.emit_signal(signal, *data)
except (socket.error, Exception), e:
log.warning("Unable to emit signal to client %s: %s (%d)", client, e, count)
if count < 30:
gobject.timeout_add(1000, self._emit, uri, signal, count + 1, *data)
else:
log.info("Removing %s because it couldn't be reached..", uri)
del self.clients[uri]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,136 +0,0 @@
#
# 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.
#
import os
import stat
from OpenSSL import crypto
from OpenSSL.crypto import FILETYPE_PEM
from twisted.internet.ssl import (
AcceptableCiphers,
Certificate,
CertificateOptions,
KeyPair,
TLSVersion,
)
import deluge.configmanager
# A TLS ciphers list.
# Sources for more information on TLS ciphers:
# - https://wiki.mozilla.org/Security/Server_Side_TLS
# - https://www.ssllabs.com/projects/best-practices/index.html
# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
#
# This list was inspired by the `urllib3` library
# - https://github.com/urllib3/urllib3/blob/master/urllib3/util/ssl_.py#L79
#
# The general intent is:
# - prefer cipher suites that offer perfect forward secrecy (ECDHE),
# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common,
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
TLS_CIPHERS = ':'.join(
[
'ECDH+AESGCM',
'ECDH+CHACHA20',
'AES256-GCM-SHA384',
'AES128-GCM-SHA256',
'!DSS' '!aNULL',
'!eNULL',
'!MD5',
]
)
# This value tells OpenSSL to disable all SSL/TLS renegotiation.
SSL_OP_NO_RENEGOTIATION = 0x40000000
def get_context_factory(cert_path, pkey_path):
"""OpenSSL context factory.
Generates an OpenSSL context factory using Twisted's CertificateOptions class.
This will keep a server cipher order.
Args:
cert_path (string): The path to the certificate file
pkey_path (string): The path to the private key file
Returns:
twisted.internet.ssl.CertificateOptions: An OpenSSL context factory
"""
with open(cert_path) as cert:
certificate = Certificate.loadPEM(cert.read()).original
with open(pkey_path) as pkey:
private_key = KeyPair.load(pkey.read(), FILETYPE_PEM).original
ciphers = AcceptableCiphers.fromOpenSSLCipherString(TLS_CIPHERS)
cert_options = CertificateOptions(
privateKey=private_key,
certificate=certificate,
raiseMinimumTo=TLSVersion.TLSv1_2,
acceptableCiphers=ciphers,
)
ctx = cert_options.getContext()
ctx.use_certificate_chain_file(cert_path)
ctx.set_options(SSL_OP_NO_RENEGOTIATION)
return cert_options
def check_ssl_keys():
"""
Check for SSL cert/key and create them if necessary
"""
ssl_dir = deluge.configmanager.get_config_dir('ssl')
if not os.path.exists(ssl_dir):
# The ssl folder doesn't exist so we need to create it
os.makedirs(ssl_dir)
generate_ssl_keys()
else:
for f in ('daemon.pkey', 'daemon.cert'):
if not os.path.exists(os.path.join(ssl_dir, f)):
generate_ssl_keys()
break
def generate_ssl_keys():
"""
This method generates a new SSL key/cert.
"""
digest = 'sha256'
# Generate key pair
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, 2048)
# Generate cert request
req = crypto.X509Req()
subj = req.get_subject()
setattr(subj, 'CN', 'Deluge Daemon')
req.set_pubkey(pkey)
req.sign(pkey, digest)
# Generate certificate
cert = crypto.X509()
cert.set_serial_number(0)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 3) # Three Years
cert.set_issuer(req.get_subject())
cert.set_subject(req.get_subject())
cert.set_pubkey(req.get_pubkey())
cert.sign(pkey, digest)
# Write out files
ssl_dir = deluge.configmanager.get_config_dir('ssl')
with open(os.path.join(ssl_dir, 'daemon.pkey'), 'wb') as _file:
_file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
with open(os.path.join(ssl_dir, 'daemon.cert'), 'wb') as _file:
_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
# Make the files only readable by this user
for f in ('daemon.pkey', 'daemon.cert'):
os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE)

View File

@ -0,0 +1,39 @@
There are two licenses, one for the C library software, and one for
the database.
SOFTWARE LICENSE (C library)
The GeoIP C Library is licensed under the LGPL. For details see
the COPYING file.
OPEN DATA LICENSE (GeoLite Country and GeoLite City databases)
Copyright (c) 2008 MaxMind, Inc. All Rights Reserved.
All advertising materials and documentation mentioning features or use of
this database must display the following acknowledgment:
"This product includes GeoLite data created by MaxMind, available from
http://maxmind.com/"
Redistribution and use with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions must retain the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
2. All advertising materials and documentation mentioning features or use of
this database must display the following acknowledgement:
"This product includes GeoLite data created by MaxMind, available from
http://maxmind.com/"
3. "MaxMind" may not be used to endorse or promote products derived from this
database without specific prior written permission.
THIS DATABASE IS PROVIDED BY MAXMIND, INC ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MAXMIND BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
DATABASE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

BIN
deluge/data/GeoIP.dat Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 722 B

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg3440"
sodipodi:version="0.32"
inkscape:version="0.45"
sodipodi:docbase="/home/zach/deluge/trunk"
sodipodi:docname="deluge.svg"
inkscape:export-filename="/home/zach/deluge.png"
inkscape:export-xdpi="480"
inkscape:export-ydpi="480"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
sodipodi:modified="TRUE">
<defs
id="defs3">
<linearGradient
inkscape:collect="always"
id="linearGradient2973">
<stop
style="stop-color:#eeeeec;stop-opacity:1;"
offset="0"
id="stop2975" />
<stop
style="stop-color:#eeeeec;stop-opacity:0;"
offset="1"
id="stop2977" />
</linearGradient>
<linearGradient
id="linearGradient4126">
<stop
style="stop-color:#ffffff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop4128" />
<stop
style="stop-color:#ffffff;stop-opacity:0.16494845;"
offset="1.0000000"
id="stop4130" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4114">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4116" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop4118" />
</linearGradient>
<linearGradient
id="linearGradient3962">
<stop
style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop3964" />
<stop
style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
offset="0.15517241"
id="stop4134" />
<stop
style="stop-color:#4074ae;stop-opacity:1.0000000;"
offset="0.75000000"
id="stop4346" />
<stop
style="stop-color:#36486c;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop3966" />
</linearGradient>
<radialGradient
r="13.994944"
fy="33.506763"
fx="-10.089286"
cy="33.506763"
cx="-10.089286"
gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
gradientUnits="userSpaceOnUse"
id="radialGradient4019"
xlink:href="#linearGradient3993"
inkscape:collect="always" />
<radialGradient
r="14.057444"
fy="31.329016"
fx="-10.323107"
cy="31.329016"
cx="-10.323107"
gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
gradientUnits="userSpaceOnUse"
id="radialGradient4004"
xlink:href="#linearGradient3993"
inkscape:collect="always" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
r="14.057444"
fy="31.329016"
fx="-10.323107"
cy="31.329016"
cx="-10.323107"
id="radialGradient3999"
xlink:href="#linearGradient3993"
inkscape:collect="always" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
r="13.994946"
fy="24.241488"
fx="61.662098"
cy="24.241488"
cx="61.662098"
id="radialGradient3943"
xlink:href="#linearGradient1312"
inkscape:collect="always" />
<linearGradient
id="linearGradient1312">
<stop
id="stop1314"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop1316"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3993">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3995" />
<stop
style="stop-color:#000000;stop-opacity:0"
offset="1"
id="stop3997" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2973"
id="radialGradient3866"
cx="-22.375"
cy="18.499998"
fx="-22.375"
fy="18.499998"
r="14.33462"
gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
gradientUnits="userSpaceOnUse" />
<radialGradient
gradientUnits="userSpaceOnUse"
r="12.289036"
fy="63.965388"
fx="15.115514"
cy="63.965388"
cx="15.115514"
gradientTransform="scale(1.643990,0.608276)"
id="radialGradient5000"
xlink:href="#linearGradient4114"
inkscape:collect="always" />
<linearGradient
id="linearGradient4989">
<stop
id="stop4991"
offset="0.0000000"
style="stop-color:#d3e9ff;stop-opacity:1.0000000;" />
<stop
id="stop4993"
offset="0.15517241"
style="stop-color:#d3e9ff;stop-opacity:1.0000000;" />
<stop
id="stop4995"
offset="0.75000000"
style="stop-color:#4074ae;stop-opacity:1.0000000;" />
<stop
id="stop4997"
offset="1.0000000"
style="stop-color:#36486c;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient4977">
<stop
id="stop4979"
offset="0.0000000"
style="stop-color:#ffffff;stop-opacity:1.0000000;" />
<stop
id="stop4981"
offset="1.0000000"
style="stop-color:#ffffff;stop-opacity:0.16494845;" />
</linearGradient>
<linearGradient
id="linearGradient4825"
inkscape:collect="always">
<stop
id="stop4827"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop4829"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4114"
id="radialGradient6090"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.64399,0.608276)"
cx="15.115514"
cy="63.965388"
fx="15.115514"
fy="63.965388"
r="12.289036" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4825"
id="radialGradient6098"
gradientUnits="userSpaceOnUse"
cx="12.071323"
cy="12.493138"
fx="12.071323"
fy="12.493138"
r="6.7175145" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2973"
id="radialGradient6103"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.25463,-0.898371,0.979785,0.277703,-18.00903,32.03312)"
cx="17.903898"
cy="40.159222"
fx="17.903898"
fy="40.159222"
r="14.33681" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2973"
id="radialGradient6106"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.583269,-0.431533,0.577146,0.78008,-5.80022,4.004109)"
cx="12.525543"
cy="38.09042"
fx="12.525543"
fy="38.09042"
r="14.33681" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient1312"
id="radialGradient6109"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.768231,1.13675,-0.820972,0.554824,-3.72248,-85.07126)"
cx="65.800331"
cy="27.16758"
fx="65.800331"
fy="27.16758"
r="12.972491" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4989"
id="radialGradient6115"
cx="16.651781"
cy="32.187485"
fx="16.651781"
fy="32.187485"
r="17.089519"
gradientTransform="matrix(1.486175,-1.536108,0.932321,0.902016,-38.10476,31.42646)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.17254902"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="8"
inkscape:cx="36.250498"
inkscape:cy="38.275489"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1266"
inkscape:window-height="944"
inkscape:window-x="124"
inkscape:window-y="52"
inkscape:showpageshadow="false" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Internet Category</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>Tuomas Kuosmanen</dc:title>
</cc:Agent>
</dc:contributor>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
<dc:source>http://jimmac.musichall.cz</dc:source>
<dc:subject>
<rdf:Bag>
<rdf:li>internet</rdf:li>
<rdf:li>tools</rdf:li>
<rdf:li>applications</rdf:li>
<rdf:li>category</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Attribution" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="fill:url(#radialGradient6090);fill-opacity:1;stroke:none;stroke-opacity:1"
id="path4112"
sodipodi:cx="24.849752"
sodipodi:cy="38.908627"
sodipodi:rx="20.203051"
sodipodi:ry="7.4751287"
d="M 45.052803 38.908627 A 20.203051 7.4751287 0 1 1 4.6467018,38.908627 A 20.203051 7.4751287 0 1 1 45.052803 38.908627 z"
transform="matrix(0.947409,0,0,1.17786,1.244375,-6.853427)"
inkscape:export-xdpi="480"
inkscape:export-ydpi="480" />
<path
style="fill:url(#radialGradient6115);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 23.942923,0.9561338 L 37.330543,18.266721 C 46.998995,29.84687 41.49692,43.923891 26.7742,45.000491 C 6.0597413,45.582655 6.5086231,27.37483 11.255313,18.609381 L 23.942923,0.9561338 z "
id="path2069"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#1b4075;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 35.111358,26.143133 C 28.972772,13.030586 17.560684,17.697957 17.274449,26.949974 C 16.894738,39.223415 34.748874,37.615429 36.715244,41.468778 C 28.821643,47.675479 14.973233,45.226508 10.962289,39.715204 C 6.9574776,34.212326 7.2383598,25.630263 10.784249,19.587632 C 24.158625,0.978654 39.749127,24.383766 35.111358,26.143133 z "
id="path2969"
sodipodi:nodetypes="cscscc" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:white;stroke-width:1.1000706;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022"
d="M 23.996861,3.5433428 L 36.057351,19.151045 C 44.769741,29.58253 39.419346,42.414092 26.125181,43.508521 C 7.3917365,44.015286 7.4275065,28.119221 12.17284,20.333442 L 23.996861,3.5433428 z "
id="path2071"
sodipodi:nodetypes="ccccc" />
<path
style="opacity:0.46;fill:url(#radialGradient6109);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 23.940758,0.96491709 L 34.727367,14.909752 C 42.647208,24.392311 40.447304,20.283975 28.362481,21.278846 C 25.083165,11.203805 18.13871,11.859899 13.523802,15.675236 L 23.940758,0.96491709 z "
id="path3945"
sodipodi:nodetypes="ccccc" />
<path
style="fill:url(#radialGradient6106);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 35.159701,26.173667 C 29.021115,13.06112 18.734027,17.978491 18.447792,27.230508 C 18.068081,39.503949 34.797217,37.645963 36.763587,41.499312 C 28.869986,47.706013 15.021576,45.257042 11.010632,39.745738 C 7.0058197,34.24286 7.2867027,25.660797 10.832592,19.618166 C 24.206968,1.0091879 39.79747,24.4143 35.159701,26.173667 z "
id="path3868"
sodipodi:nodetypes="cscscc" />
<path
style="fill:url(#radialGradient6103);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 35.120795,26.14195 C 28.553327,12.814962 15.685968,17.224233 15.399733,26.47625 C 15.020022,38.749691 32.874158,37.141705 34.840528,40.995054 C 26.946927,47.201755 13.098517,44.752784 9.0875727,39.24148 C 5.0827617,33.738602 5.3636437,25.156539 8.9095327,19.113908 C 22.315509,0.47615954 40.03233,23.660113 35.120795,26.14195 z "
id="path4874"
sodipodi:nodetypes="cscscc" />
<path
transform="matrix(-0.829136,1.052307,1.239307,7.58326e-2,26.32898,25.58605)"
inkscape:r_cy="true"
inkscape:r_cx="true"
d="M 18.788838 12.493138 A 6.7175145 6.7175145 0 1 1 5.3538089,12.493138 A 6.7175145 6.7175145 0 1 1 18.788838 12.493138 z"
sodipodi:ry="6.7175145"
sodipodi:rx="6.7175145"
sodipodi:cy="12.493138"
sodipodi:cx="12.071323"
id="path4941"
style="opacity:0.21999996;color:black;fill:url(#radialGradient6098);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg4848"
sodipodi:version="0.32"
inkscape:version="0.46"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/active16.png"
inkscape:export-xdpi="1.7055545"
inkscape:export-ydpi="1.7055545"
sodipodi:docname="active.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs4850">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective4856" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath5467">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.69776249;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 792.51261,596.13496 C 1787.1961,462.38583 1788.2786,460.03989 1788.2786,460.03989 L 2050.3447,889.51678 C 1692.1626,1024.6476 1428.8778,1128.1462 1119.6165,1246.9702 L 792.51261,596.13496 z"
id="path5469"
sodipodi:nodetypes="ccccc" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath5471">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.69776249;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 792.51261,596.13496 C 1787.1961,462.38583 1788.2786,460.03989 1788.2786,460.03989 L 2050.3447,889.51678 C 1692.1626,1024.6476 1428.8778,1128.1462 1119.6165,1246.9702 L 792.51261,596.13496 z"
id="path5473"
sodipodi:nodetypes="ccccc" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath5475">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.69776249;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 792.51261,596.13496 C 1787.1961,462.38583 1788.2786,460.03989 1788.2786,460.03989 L 2050.3447,889.51678 C 1692.1626,1024.6476 1428.8778,1128.1462 1119.6165,1246.9702 L 792.51261,596.13496 z"
id="path5477"
sodipodi:nodetypes="ccccc" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.5"
inkscape:cx="798.86898"
inkscape:cy="436.74575"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1280"
inkscape:window-height="958"
inkscape:window-x="1280"
inkscape:window-y="0" />
<metadata
id="metadata4853">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
style="opacity:1"
id="g4871">
<path
inkscape:export-ydpi="1.7060417"
inkscape:export-xdpi="1.7060417"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
sodipodi:nodetypes="ccccc"
id="path2069"
d="M 335.10794,116.43168 L 610.53935,447.76408 C 809.45408,669.41324 696.25675,938.85368 393.35753,959.46028 C -32.813273,970.60316 -9.7709676,623.52458 107.82928,464.31364 L 335.10794,116.43168 z"
style="fill:#5599ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:export-ydpi="1.7060417"
inkscape:export-xdpi="1.7060417"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
sodipodi:nodetypes="ccccc"
id="path2071"
d="M 337.67292,165.87552 L 585.22638,464.67366 C 773.96211,684.57988 653.37942,910.02774 383.88559,930.97978 C 4.1291863,940.68143 29.040179,639.21796 125.2355,490.1649 L 337.67292,165.87552 z"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:21.67123985;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022" />
<path
inkscape:export-ydpi="1.7060417"
inkscape:export-xdpi="1.7060417"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
id="rect2634"
d="M 359.0208,369.47132 C 289.64507,437.53055 219.98727,505.41409 150.61154,573.47313 C 187.93874,573.54143 225.35922,573.40503 262.68661,573.47313 C 262.68661,654.06643 262.68661,734.65974 262.68661,815.25304 C 329.00806,815.25304 395.32971,815.25304 461.65134,815.25304 C 461.65134,734.86969 461.65134,654.48632 461.65134,574.10276 C 496.04273,574.16582 530.52013,574.0399 564.91152,574.10276 C 496.30183,505.79126 427.63028,437.78301 359.0208,369.47132 z"
style="fill:#93beff;fill-opacity:1;stroke:#000000;stroke-width:14.10382843;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<path
style="fill:#8dd35f;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 1211.4629,48.95108 L 1486.8943,380.28348 C 1685.809,601.93264 1572.6117,871.37308 1269.7125,891.97968 C 843.5417,903.12256 866.584,556.04398 984.1842,396.83304 L 1211.4629,48.95108 z"
id="path4864"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417"
clip-path="url(#clipPath5475)"
transform="translate(-876,68)" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:21.67123985;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022"
d="M 1214.0279,98.39492 L 1461.5813,397.19306 C 1650.317,617.09928 1529.7344,842.54714 1260.2405,863.49918 C 880.4842,873.20083 905.3951,571.73736 1001.5905,422.6843 L 1214.0279,98.39492 z"
id="path4866"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417"
clip-path="url(#clipPath5471)"
transform="translate(-876,68)" />
<path
style="fill:#b7e399;fill-opacity:1;stroke:#000000;stroke-width:14.1288912;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 1235.158,785.66485 C 1165.4485,716.69801 1095.4555,647.90922 1025.7459,578.94258 C 1063.2527,578.87336 1100.8533,579.01159 1138.3603,578.94258 C 1138.3603,497.27453 1138.3603,415.60647 1138.3603,333.93841 C 1205.0008,333.93841 1271.6416,333.93841 1338.2823,333.93841 C 1338.2823,415.39371 1338.2823,496.84903 1338.2823,578.30455 C 1372.8392,578.24065 1407.4825,578.36825 1442.0394,578.30455 C 1373.0995,647.52701 1304.0976,716.44219 1235.158,785.66485 z"
id="path4868"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417"
clip-path="url(#clipPath5467)"
transform="matrix(0.9958235,0,0,1.0000914,-871.02338,67.589213)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg5684"
sodipodi:version="0.32"
inkscape:version="0.46"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/alert16.png"
inkscape:export-xdpi="1.7055545"
inkscape:export-ydpi="1.7055545"
sodipodi:docname="alert.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs5686">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective5692" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="807.63627"
inkscape:cy="520"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="686"
inkscape:window-height="711"
inkscape:window-x="1395"
inkscape:window-y="223" />
<metadata
id="metadata5689">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 335.10794,116.43168 L 610.53935,447.76408 C 809.45408,669.41324 696.25675,938.85368 393.35753,959.46028 C -32.813273,970.60316 -9.7709676,623.52458 107.82928,464.31364 L 335.10794,116.43168 z"
id="path2069"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:21.67123984999999919;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022000000000"
d="M 337.67292,165.87552 L 585.22638,464.67366 C 773.96211,684.57988 653.37942,910.02774 383.88559,930.97978 C 4.1291863,940.68143 29.040179,639.21796 125.2355,490.1649 L 337.67292,165.87552 z"
id="path2071"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417" />
<text
xml:space="preserve"
style="font-size:1091.49255371px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#ff7878;fill-opacity:1;stroke:#000000;stroke-width:13.65081787;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
x="20.453644"
y="1303.4996"
id="text2767"
transform="scale(1.4928735,0.6698491)"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan2769"
x="20.453644"
y="1303.4996"
style="font-size:1091.49255371px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;stroke-width:13.65081787;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;font-family:Bitstream Vera Sans">!</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

191
deluge/data/pixmaps/all.svg Normal file
View File

@ -0,0 +1,191 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg3001"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docname="all.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs3003">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective3009" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="375"
inkscape:cy="520"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="640"
inkscape:window-height="711"
inkscape:window-x="1594"
inkscape:window-y="77" />
<metadata
id="metadata3006">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g3011"
transform="matrix(7.4849593,0,0,7.4849593,52.997244,360.31942)"
inkscape:export-filename="/home/andrew/g3060.png"
inkscape:export-xdpi="2.4250078"
inkscape:export-ydpi="2.4250078">
<path
sodipodi:nodetypes="ccccc"
id="path2069"
d="M 23.942923,0.9561338 L 37.330543,18.266721 C 46.998995,29.84687 41.49692,43.923891 26.7742,45.000491 C 6.0597413,45.582655 6.5086231,27.37483 11.255313,18.609381 L 23.942923,0.9561338 z"
style="fill:#ff0000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
id="path2071"
d="M 23.996861,3.5433428 L 36.057351,19.151045 C 44.769741,29.58253 39.419346,42.414092 26.125181,43.508521 C 7.3917365,44.015286 7.4275065,28.119221 12.17284,20.333442 L 23.996861,3.5433428 z"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.1000706;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022" />
<text
sodipodi:linespacing="125%"
transform="scale(1.4928735,0.6698491)"
id="text2767"
y="59.560848"
x="5.0072942"
style="font-size:55.97062302px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;opacity:1;fill:#ff7878;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
style="font-size:55.97062302px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;font-family:Bitstream Vera Sans"
y="59.560848"
x="5.0072942"
id="tspan2769"
sodipodi:role="line">!</tspan></text>
</g>
<g
id="g3022"
transform="matrix(7.874057,0,0,7.874057,232.39499,356.39427)"
inkscape:export-filename="/home/andrew/g3060.png"
inkscape:export-xdpi="2.4250078"
inkscape:export-ydpi="2.4250078">
<path
sodipodi:nodetypes="ccccc"
id="path3030"
d="M 23.854535,1.0445221 L 37.242155,18.355109 C 46.910607,29.935258 41.408532,44.012279 26.685812,45.088879 C 5.971353,45.671043 6.4202348,27.463218 11.166925,18.697769 L 23.854535,1.0445221 z"
style="fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
id="path3032"
d="M 23.996861,3.5433428 L 36.057351,19.151045 C 44.769741,29.58253 39.419346,42.414092 26.125181,43.508521 C 7.3917365,44.015286 7.4275065,28.119221 12.17284,20.333442 L 23.996861,3.5433428 z"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.1000706;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022" />
<g
transform="matrix(0.9616363,0,0,0.855461,0.9207282,3.9296533)"
id="g3590">
<rect
style="fill:#cfcfcf;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4415"
width="7.7589664"
height="14.866735"
x="14.548919"
y="19.754131" />
<rect
style="fill:#cfcfcf;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4417"
width="7.7589664"
height="14.866735"
x="25.692116"
y="19.754131" />
</g>
</g>
<g
id="g3037"
transform="matrix(8.0037561,0,0,8.0037561,-22.312009,464.83023)"
inkscape:export-filename="/home/andrew/g3060.png"
inkscape:export-xdpi="2.4250078"
inkscape:export-ydpi="2.4250078">
<path
sodipodi:nodetypes="ccccc"
id="path3043"
d="M 23.854535,1.0445221 L 37.242155,18.355109 C 46.910607,29.935258 41.408532,44.012279 26.685812,45.088879 C 5.971353,45.671043 6.4202348,27.463218 11.166925,18.697769 L 23.854535,1.0445221 z"
style="fill:#d0b31d;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
id="path3045"
d="M 23.996861,3.5433428 L 36.057351,19.151045 C 44.769741,29.58253 39.419346,42.414092 26.125181,43.508521 C 7.3917365,44.015286 7.4275065,28.119221 12.17284,20.333442 L 23.996861,3.5433428 z"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.1000706;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022" />
<path
id="rect2634"
d="M 24.821384,15.512309 C 22.21433,17.952223 19.604921,20.381297 16.997867,22.821211 C 18.304685,22.818964 19.61477,22.823457 20.921589,22.821211 C 20.921589,25.684801 20.921589,28.548391 20.921589,31.411981 C 19.61477,31.409734 18.304685,31.414228 16.997867,31.411981 C 19.604921,33.851895 22.21433,36.280969 24.821384,38.720883 C 27.457552,36.289986 30.104432,33.865368 32.740602,31.43447 C 31.322226,31.432031 29.900305,31.43691 28.481929,31.43447 C 28.481929,28.555887 28.481929,25.677306 28.481929,22.798722 C 29.900305,22.796282 31.322226,22.80116 32.740602,22.798722 C 30.104432,20.367823 27.457552,17.943206 24.821384,15.512309 z"
style="fill:#e1cf6f;fill-opacity:1;stroke:#000000;stroke-width:0.56882578;stroke-linecap:round;stroke-linejoin:round;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
<g
id="g3048"
transform="matrix(7.6146585,0,0,7.6146585,318.27879,494.48113)"
inkscape:export-filename="/home/andrew/g3060.png"
inkscape:export-xdpi="2.4250078"
inkscape:export-ydpi="2.4250078">
<path
sodipodi:nodetypes="ccccc"
id="path3054"
d="M 23.942923,0.9561338 L 37.330543,18.266721 C 46.998995,29.84687 41.49692,43.923891 26.7742,45.000491 C 6.0597413,45.582655 6.5086231,27.37483 11.255313,18.609381 L 23.942923,0.9561338 z"
style="fill:#5599ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
id="path3056"
d="M 23.996861,3.5433428 L 36.057351,19.151045 C 44.769741,29.58253 39.419346,42.414092 26.125181,43.508521 C 7.3917365,44.015286 7.4275065,28.119221 12.17284,20.333442 L 23.996861,3.5433428 z"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.1000706;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022" />
<path
id="path3058"
d="M 24.3125,14.9375 C 20.869248,18.315409 17.412003,21.684591 13.96875,25.0625 C 15.821373,25.065889 17.678627,25.059111 19.53125,25.0625 C 19.53125,29.0625 19.53125,33.0625 19.53125,37.0625 C 22.822917,37.0625 26.114582,37.0625 29.40625,37.0625 C 29.40625,33.072917 29.40625,29.083333 29.40625,25.09375 C 31.113161,25.096872 32.824339,25.090627 34.53125,25.09375 C 31.126025,21.703313 27.717725,18.327937 24.3125,14.9375 z"
style="fill:#93beff;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
id="g3060"
transform="matrix(8.2631545,0,0,8.2631545,133.25792,542.4325)"
inkscape:export-xdpi="2.4250078"
inkscape:export-ydpi="2.4250078">
<path
sodipodi:nodetypes="ccccc"
id="path3066"
d="M 23.942923,0.9561338 L 37.330543,18.266721 C 46.998995,29.84687 41.49692,43.923891 26.7742,45.000491 C 6.0597413,45.582655 6.5086231,27.37483 11.255313,18.609381 L 23.942923,0.9561338 z"
style="fill:#8dd35f;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
id="path3068"
d="M 23.996861,3.5433428 L 36.057351,19.151045 C 44.769741,29.58253 39.419346,42.414092 26.125181,43.508521 C 7.3917365,44.015286 7.4275065,28.119221 12.17284,20.333442 L 23.996861,3.5433428 z"
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.1000706;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022" />
<path
id="path3070"
d="M 19.34375,17.5625 C 19.34375,21.552083 19.34375,25.541668 19.34375,29.53125 C 17.636839,29.528128 15.925661,29.534372 14.21875,29.53125 C 17.623975,32.921687 21.032275,36.297063 24.4375,39.6875 C 27.880752,36.309591 31.337996,32.94041 34.78125,29.5625 C 32.928627,29.559111 31.071373,29.565889 29.21875,29.5625 C 29.21875,25.5625 29.21875,21.562501 29.21875,17.5625 C 25.927083,17.5625 22.635418,17.562501 19.34375,17.5625 z"
style="fill:#b7e399;fill-opacity:1;stroke:#000000;stroke-width:0.69999999;stroke-linecap:round;stroke-linejoin:round;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg6930"
sodipodi:version="0.32"
inkscape:version="0.46"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/checking16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417"
sodipodi:docname="checking.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs6932">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective6938" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="284.02575"
inkscape:cy="416.27643"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="686"
inkscape:window-height="711"
inkscape:window-x="1387"
inkscape:window-y="128" />
<metadata
id="metadata6935">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 335.10794,116.43168 L 610.53935,447.76408 C 809.45408,669.41324 696.25675,938.85368 393.35753,959.46028 C -32.813273,970.60316 -9.7709676,623.52458 107.82928,464.31364 L 335.10794,116.43168 z"
id="path2069"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/checking16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:21.67123984999999919;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022000000000"
d="M 337.67292,165.87552 L 585.22638,464.67366 C 773.96211,684.57988 653.37942,910.02774 383.88559,930.97978 C 4.1291863,940.68143 29.040179,639.21796 125.2355,490.1649 L 337.67292,165.87552 z"
id="path2071"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417" />
<path
style="fill:#cfcfcf;fill-opacity:1;stroke:#000000;stroke-width:11.50416565;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 170.93275,447.12091 C 174.01359,451.92804 181.6932,659.85017 179.71907,656.9136 C 178.40947,654.12718 411.7223,649.25335 412.30092,648.76646 C 411.08134,648.85003 338.31478,584.63588 338.93986,585.53435 C 410.72174,567.77973 462.65633,588.73801 481.03861,645.01437 C 484.19544,690.33553 466.3409,711.71891 434.77285,736.14895 C 385.89389,754.17146 322.59241,745.84878 312.37017,673.09575 C 312.32389,674.48492 156.52907,675.28833 153.0572,676.51233 C 202.03372,799.5504 269.9646,882.62913 421.73005,870.01325 C 463.0283,867.02946 568.27041,836.89042 606.41655,717.64626 C 627.84208,623.51263 596.80367,549.4096 547.34944,496.26853 C 457.7753,416.4389 296.11292,421.8444 245.11698,507.64417 C 247.55313,508.23771 173.58263,449.35852 170.93275,447.12091 z"
id="path3485"
sodipodi:nodetypes="ccccccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,402 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48px"
height="48px"
id="svg3440"
sodipodi:version="0.32"
inkscape:version="0.45"
sodipodi:docbase="/home/zach/deluge/trunk"
sodipodi:docname="deluge.svg"
inkscape:export-filename="/home/zach/deluge.png"
inkscape:export-xdpi="480"
inkscape:export-ydpi="480"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
sodipodi:modified="TRUE">
<defs
id="defs3">
<linearGradient
inkscape:collect="always"
id="linearGradient2973">
<stop
style="stop-color:#eeeeec;stop-opacity:1;"
offset="0"
id="stop2975" />
<stop
style="stop-color:#eeeeec;stop-opacity:0;"
offset="1"
id="stop2977" />
</linearGradient>
<linearGradient
id="linearGradient4126">
<stop
style="stop-color:#ffffff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop4128" />
<stop
style="stop-color:#ffffff;stop-opacity:0.16494845;"
offset="1.0000000"
id="stop4130" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4114">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4116" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop4118" />
</linearGradient>
<linearGradient
id="linearGradient3962">
<stop
style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop3964" />
<stop
style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
offset="0.15517241"
id="stop4134" />
<stop
style="stop-color:#4074ae;stop-opacity:1.0000000;"
offset="0.75000000"
id="stop4346" />
<stop
style="stop-color:#36486c;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop3966" />
</linearGradient>
<radialGradient
r="13.994944"
fy="33.506763"
fx="-10.089286"
cy="33.506763"
cx="-10.089286"
gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
gradientUnits="userSpaceOnUse"
id="radialGradient4019"
xlink:href="#linearGradient3993"
inkscape:collect="always" />
<radialGradient
r="14.057444"
fy="31.329016"
fx="-10.323107"
cy="31.329016"
cx="-10.323107"
gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
gradientUnits="userSpaceOnUse"
id="radialGradient4004"
xlink:href="#linearGradient3993"
inkscape:collect="always" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
r="14.057444"
fy="31.329016"
fx="-10.323107"
cy="31.329016"
cx="-10.323107"
id="radialGradient3999"
xlink:href="#linearGradient3993"
inkscape:collect="always" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
r="13.994946"
fy="24.241488"
fx="61.662098"
cy="24.241488"
cx="61.662098"
id="radialGradient3943"
xlink:href="#linearGradient1312"
inkscape:collect="always" />
<linearGradient
id="linearGradient1312">
<stop
id="stop1314"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop1316"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient3993">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3995" />
<stop
style="stop-color:#000000;stop-opacity:0"
offset="1"
id="stop3997" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2973"
id="radialGradient3866"
cx="-22.375"
cy="18.499998"
fx="-22.375"
fy="18.499998"
r="14.33462"
gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
gradientUnits="userSpaceOnUse" />
<radialGradient
gradientUnits="userSpaceOnUse"
r="12.289036"
fy="63.965388"
fx="15.115514"
cy="63.965388"
cx="15.115514"
gradientTransform="scale(1.643990,0.608276)"
id="radialGradient5000"
xlink:href="#linearGradient4114"
inkscape:collect="always" />
<linearGradient
id="linearGradient4989">
<stop
id="stop4991"
offset="0.0000000"
style="stop-color:#d3e9ff;stop-opacity:1.0000000;" />
<stop
id="stop4993"
offset="0.15517241"
style="stop-color:#d3e9ff;stop-opacity:1.0000000;" />
<stop
id="stop4995"
offset="0.75000000"
style="stop-color:#4074ae;stop-opacity:1.0000000;" />
<stop
id="stop4997"
offset="1.0000000"
style="stop-color:#36486c;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient4977">
<stop
id="stop4979"
offset="0.0000000"
style="stop-color:#ffffff;stop-opacity:1.0000000;" />
<stop
id="stop4981"
offset="1.0000000"
style="stop-color:#ffffff;stop-opacity:0.16494845;" />
</linearGradient>
<linearGradient
id="linearGradient4825"
inkscape:collect="always">
<stop
id="stop4827"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop4829"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4114"
id="radialGradient6090"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.64399,0.608276)"
cx="15.115514"
cy="63.965388"
fx="15.115514"
fy="63.965388"
r="12.289036" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4825"
id="radialGradient6098"
gradientUnits="userSpaceOnUse"
cx="12.071323"
cy="12.493138"
fx="12.071323"
fy="12.493138"
r="6.7175145" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2973"
id="radialGradient6103"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.25463,-0.898371,0.979785,0.277703,-18.00903,32.03312)"
cx="17.903898"
cy="40.159222"
fx="17.903898"
fy="40.159222"
r="14.33681" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2973"
id="radialGradient6106"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.583269,-0.431533,0.577146,0.78008,-5.80022,4.004109)"
cx="12.525543"
cy="38.09042"
fx="12.525543"
fy="38.09042"
r="14.33681" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient1312"
id="radialGradient6109"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.768231,1.13675,-0.820972,0.554824,-3.72248,-85.07126)"
cx="65.800331"
cy="27.16758"
fx="65.800331"
fy="27.16758"
r="12.972491" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4989"
id="radialGradient6115"
cx="16.651781"
cy="32.187485"
fx="16.651781"
fy="32.187485"
r="17.089519"
gradientTransform="matrix(1.486175,-1.536108,0.932321,0.902016,-38.10476,31.42646)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.17254902"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="8"
inkscape:cx="36.250498"
inkscape:cy="38.275489"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1266"
inkscape:window-height="944"
inkscape:window-x="124"
inkscape:window-y="52"
inkscape:showpageshadow="false" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Internet Category</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:contributor>
<cc:Agent>
<dc:title>Tuomas Kuosmanen</dc:title>
</cc:Agent>
</dc:contributor>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
<dc:source>http://jimmac.musichall.cz</dc:source>
<dc:subject>
<rdf:Bag>
<rdf:li>internet</rdf:li>
<rdf:li>tools</rdf:li>
<rdf:li>applications</rdf:li>
<rdf:li>category</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Attribution" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
sodipodi:type="arc"
style="fill:url(#radialGradient6090);fill-opacity:1;stroke:none;stroke-opacity:1"
id="path4112"
sodipodi:cx="24.849752"
sodipodi:cy="38.908627"
sodipodi:rx="20.203051"
sodipodi:ry="7.4751287"
d="M 45.052803 38.908627 A 20.203051 7.4751287 0 1 1 4.6467018,38.908627 A 20.203051 7.4751287 0 1 1 45.052803 38.908627 z"
transform="matrix(0.947409,0,0,1.17786,1.244375,-6.853427)"
inkscape:export-xdpi="480"
inkscape:export-ydpi="480" />
<path
style="fill:url(#radialGradient6115);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 23.942923,0.9561338 L 37.330543,18.266721 C 46.998995,29.84687 41.49692,43.923891 26.7742,45.000491 C 6.0597413,45.582655 6.5086231,27.37483 11.255313,18.609381 L 23.942923,0.9561338 z "
id="path2069"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#1b4075;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 35.111358,26.143133 C 28.972772,13.030586 17.560684,17.697957 17.274449,26.949974 C 16.894738,39.223415 34.748874,37.615429 36.715244,41.468778 C 28.821643,47.675479 14.973233,45.226508 10.962289,39.715204 C 6.9574776,34.212326 7.2383598,25.630263 10.784249,19.587632 C 24.158625,0.978654 39.749127,24.383766 35.111358,26.143133 z "
id="path2969"
sodipodi:nodetypes="cscscc" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:white;stroke-width:1.1000706;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022"
d="M 23.996861,3.5433428 L 36.057351,19.151045 C 44.769741,29.58253 39.419346,42.414092 26.125181,43.508521 C 7.3917365,44.015286 7.4275065,28.119221 12.17284,20.333442 L 23.996861,3.5433428 z "
id="path2071"
sodipodi:nodetypes="ccccc" />
<path
style="opacity:0.46;fill:url(#radialGradient6109);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 23.940758,0.96491709 L 34.727367,14.909752 C 42.647208,24.392311 40.447304,20.283975 28.362481,21.278846 C 25.083165,11.203805 18.13871,11.859899 13.523802,15.675236 L 23.940758,0.96491709 z "
id="path3945"
sodipodi:nodetypes="ccccc" />
<path
style="fill:url(#radialGradient6106);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 35.159701,26.173667 C 29.021115,13.06112 18.734027,17.978491 18.447792,27.230508 C 18.068081,39.503949 34.797217,37.645963 36.763587,41.499312 C 28.869986,47.706013 15.021576,45.257042 11.010632,39.745738 C 7.0058197,34.24286 7.2867027,25.660797 10.832592,19.618166 C 24.206968,1.0091879 39.79747,24.4143 35.159701,26.173667 z "
id="path3868"
sodipodi:nodetypes="cscscc" />
<path
style="fill:url(#radialGradient6103);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 35.120795,26.14195 C 28.553327,12.814962 15.685968,17.224233 15.399733,26.47625 C 15.020022,38.749691 32.874158,37.141705 34.840528,40.995054 C 26.946927,47.201755 13.098517,44.752784 9.0875727,39.24148 C 5.0827617,33.738602 5.3636437,25.156539 8.9095327,19.113908 C 22.315509,0.47615954 40.03233,23.660113 35.120795,26.14195 z "
id="path4874"
sodipodi:nodetypes="cscscc" />
<path
transform="matrix(-0.829136,1.052307,1.239307,7.58326e-2,26.32898,25.58605)"
inkscape:r_cy="true"
inkscape:r_cx="true"
d="M 18.788838 12.493138 A 6.7175145 6.7175145 0 1 1 5.3538089,12.493138 A 6.7175145 6.7175145 0 1 1 18.788838 12.493138 z"
sodipodi:ry="6.7175145"
sodipodi:rx="6.7175145"
sodipodi:cy="12.493138"
sodipodi:cx="12.071323"
id="path4941"
style="opacity:0.21999996;color:black;fill:url(#radialGradient6098);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
sodipodi:type="arc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,433 @@
/* XPM */
static char * deluge_xpm[] = {
"32 32 398 2",
" c None",
". c #7B869E",
"+ c #7B869D",
"@ c #A5ADBE",
"# c #A7AFBE",
"$ c #8894A9",
"% c #A4ADBD",
"& c #A7AFBF",
"* c #95A0B4",
"= c #6F7E99",
"- c #A0AABD",
"; c #8594AD",
"> c #8A98B1",
", c #A7B3C6",
"' c #8496AF",
") c #5B6F90",
"! c #8F9EB6",
"~ c #8194AF",
"{ c #7C91AF",
"] c #8096B3",
"^ c #859BB8",
"/ c #A0B1C8",
"( c #728CAD",
"_ c #758BAA",
": c #849AB7",
"< c #6F8AAE",
"[ c #7591B3",
"} c #7995B8",
"| c #7A97B9",
"1 c #86A0C0",
"2 c #93ABC7",
"3 c #6585AE",
"4 c #5A78A0",
"5 c #879FBE",
"6 c #6485AF",
"7 c #698CB5",
"8 c #6E91BA",
"9 c #7295BD",
"0 c #7497BF",
"a c #7296BF",
"b c #89A6C8",
"c c #809FC4",
"d c #436B9C",
"e c #7B99BE",
"f c #5378A6",
"g c #446A9A",
"h c #4B6F9C",
"i c #5577A2",
"j c #6183AE",
"k c #6D92BD",
"l c #7097C3",
"m c #6E96C1",
"n c #8DACCF",
"o c #6C93BF",
"p c #587DA9",
"q c #446592",
"r c #1B4075",
"s c #204478",
"t c #2F5081",
"u c #496791",
"v c #4F6B94",
"w c #5879A4",
"x c #6B93BE",
"y c #6E97C2",
"z c #8EADCF",
"A c #5E89BA",
"B c #3E669A",
"C c #567198",
"D c #24477A",
"E c #315281",
"F c #365685",
"G c #3A5A86",
"H c #405E8A",
"I c #567197",
"J c #577298",
"K c #516E97",
"L c #6187B3",
"M c #7098C4",
"N c #85A7CC",
"O c #5280B6",
"P c #305C92",
"Q c #5A759B",
"R c #2B4D7E",
"S c #3B5A88",
"T c #4C6991",
"U c #597397",
"V c #60799D",
"W c #5C769A",
"X c #587298",
"Y c #637C9E",
"Z c #5D769B",
"` c #526F96",
" . c #5B84B0",
".. c #759BC6",
"+. c #7398C4",
"@. c #325D95",
"#. c #4C6A94",
"$. c #3B5B87",
"%. c #44628C",
"&. c #5B7599",
"*. c #6D84A5",
"=. c #647D9F",
"-. c #6784A8",
";. c #7194BD",
">. c #769EC9",
",. c #709AC4",
"'. c #7293B8",
"). c #6D86A9",
"!. c #526E96",
"~. c #5782B1",
"{. c #7A9EC8",
"]. c #5D8ABC",
"^. c #325485",
"/. c #4F6C94",
"(. c #3D5C88",
"_. c #5E779B",
":. c #7489A8",
"<. c #667FA1",
"[. c #8BA3C1",
"}. c #8BAED4",
"|. c #83A9D3",
"1. c #82A8D2",
"2. c #7FA6D1",
"3. c #7CA4CE",
"4. c #7AA0CC",
"5. c #708EB4",
"6. c #5F789C",
"7. c #4E6C96",
"8. c #5986B9",
"9. c #7FA2CA",
"0. c #4B7CB2",
"a. c #2B5387",
"b. c #567199",
"c. c #305282",
"d. c #506B93",
"e. c #7187A5",
"f. c #6F85A3",
"g. c #8EA4BF",
"h. c #9DBBDC",
"i. c #8EB2D9",
"j. c #8DB1D8",
"k. c #8AAED6",
"l. c #84AAD3",
"m. c #7EA5D0",
"n. c #779FCC",
"o. c #6C8BB0",
"p. c #5E779A",
"q. c #5178A6",
"r. c #5C89BC",
"s. c #7198C4",
"t. c #325486",
"u. c #4C6993",
"v. c #5E789B",
"w. c #778CA8",
"x. c #7389A7",
"y. c #B6CADF",
"z. c #9ABBDF",
"A. c #9BBCE0",
"B. c #9ABCDF",
"C. c #97B9DE",
"D. c #93B6DB",
"E. c #8CB1D8",
"F. c #85ABD4",
"G. c #7CA4CF",
"H. c #749BC9",
"I. c #6783A6",
"J. c #56739C",
"K. c #5283B8",
"L. c #6F96C3",
"M. c #4E7BAE",
"N. c #42618D",
"O. c #3E5C88",
"P. c #667E9F",
"Q. c #7288A7",
"R. c #8EA1BA",
"S. c #B9CFE7",
"T. c #A6C5E6",
"U. c #A7C6E6",
"V. c #A5C4E6",
"W. c #A1C1E3",
"X. c #93B6DC",
"Y. c #8AAFD7",
"Z. c #80A7D1",
"`. c #769FCC",
" + c #6992BF",
".+ c #5D789D",
"++ c #5483B9",
"@+ c #5281B7",
"#+ c #688FBB",
"$+ c #557198",
"%+ c #1E4277",
"&+ c #3F5E88",
"*+ c #687FA0",
"=+ c #6F85A5",
"-+ c #A0B2C8",
";+ c #BFD5ED",
">+ c #B3CFED",
",+ c #B2CFED",
"'+ c #AFCCEB",
")+ c #A9C8E8",
"!+ c #A2C2E4",
"~+ c #99BADF",
"{+ c #78A1CD",
"]+ c #6C97C6",
"^+ c #5D88B7",
"/+ c #4779B2",
"(+ c #7295BE",
"_+ c #3C6699",
":+ c #2B4F83",
"<+ c #59749A",
"[+ c #657D9E",
"}+ c #6C83A3",
"|+ c #A3B4CA",
"1+ c #C7DDF2",
"2+ c #BFD9F4",
"3+ c #BDD7F2",
"4+ c #B8D3F0",
"5+ c #B0CDEC",
"6+ c #9CBDE0",
"7+ c #91B4DA",
"8+ c #79A1CD",
"9+ c #5F8DBF",
"0+ c #4578B1",
"a+ c #6B90BA",
"b+ c #446A9B",
"c+ c #2D5184",
"d+ c #577299",
"e+ c #305181",
"f+ c #5B749A",
"g+ c #6C82A2",
"h+ c #98AAC3",
"i+ c #D2E5F6",
"j+ c #CAE2FA",
"k+ c #C6DEF8",
"l+ c #BED8F3",
"m+ c #B4D0EE",
"n+ c #9EBEE1",
"o+ c #92B5DB",
"p+ c #78A0CD",
"q+ c #6B96C5",
"r+ c #5D8BBE",
"s+ c #5081B7",
"t+ c #4276AF",
"u+ c #6489B4",
"v+ c #4B6E9B",
"w+ c #2C5284",
"x+ c #234679",
"y+ c #4C6990",
"z+ c #6C82A3",
"A+ c #738AA9",
"B+ c #D9E9F9",
"C+ c #D3E9FF",
"D+ c #CCE3FB",
"E+ c #C2DBF5",
"F+ c #B6D2EF",
"G+ c #AAC8E8",
"H+ c #9DBEE1",
"I+ c #90B3DA",
"J+ c #759ECB",
"K+ c #6893C4",
"L+ c #5A89BC",
"M+ c #4D7EB5",
"N+ c #3F73AD",
"O+ c #6386B1",
"P+ c #486A96",
"Q+ c #2A5082",
"R+ c #58739A",
"S+ c #375685",
"T+ c #5F789B",
"U+ c #ADBED3",
"V+ c #D4E9FE",
"W+ c #CFE6FD",
"X+ c #C2DCF6",
"Y+ c #A8C7E7",
"Z+ c #719BC9",
"`+ c #6490C1",
" @ c #5685BA",
".@ c #487AB2",
"+@ c #3F70A8",
"@@ c #6B8BB2",
"#@ c #3F608D",
"$@ c #536F97",
"%@ c #1F4377",
"&@ c #224679",
"*@ c #46638D",
"=@ c #6880A0",
"-@ c #6F86A5",
";@ c #BBCDE0",
">@ c #CEE4FC",
",@ c #C0DAF4",
"'@ c #A5C4E5",
")@ c #96B9DD",
"!@ c #88AED6",
"~@ c #7CA3CE",
"{@ c #719BC8",
"]@ c #6692C2",
"^@ c #5987BA",
"/@ c #4C7CB3",
"(@ c #4672A5",
"_@ c #728DB1",
":@ c #385884",
"<@ c #3C5C89",
"[@ c #385886",
"}@ c #26497B",
"|@ c #4B6790",
"1@ c #6980A1",
"2@ c #768BA9",
"3@ c #9CB1CA",
"4@ c #B9D3EE",
"5@ c #ADCAEA",
"6@ c #9EBFE2",
"7@ c #90B4DA",
"8@ c #87ACD5",
"9@ c #7EA4CE",
"0@ c #749CC8",
"a@ c #6993C1",
"b@ c #5C89BB",
"c@ c #4F7DB1",
"d@ c #5D80AC",
"e@ c #617EA4",
"f@ c #3A547B",
"g@ c #2A4C7E",
"h@ c #1C4176",
"i@ c #27497B",
"j@ c #45628D",
"k@ c #60799C",
"l@ c #7288A6",
"m@ c #7D91AD",
"n@ c #859FBE",
"o@ c #93B4D7",
"p@ c #8FB2D8",
"q@ c #86ABD2",
"r@ c #7EA4CD",
"s@ c #759CC8",
"t@ c #6A94C2",
"u@ c #5C89BA",
"v@ c #547EAE",
"w@ c #7994B6",
"x@ c #44628A",
"y@ c #375786",
"z@ c #476590",
"A@ c #4C6890",
"B@ c #5B7498",
"C@ c #667EA0",
"D@ c #6F86A7",
"E@ c #738CAD",
"F@ c #7592B7",
"G@ c #779AC1",
"H@ c #779DC7",
"I@ c #6993C0",
"J@ c #5B86B6",
"K@ c #7B97BB",
"L@ c #5A769E",
"M@ c #394E6E",
"N@ c #21426C",
"O@ c #45638E",
"P@ c #496691",
"Q@ c #1D4176",
"R@ c #41608B",
"S@ c #4C6891",
"T@ c #506D95",
"U@ c #4E6B94",
"V@ c #4B6A95",
"W@ c #5176A3",
"X@ c #7C99BC",
"Y@ c #6280A8",
"Z@ c #3E567A",
"`@ c #25446F",
" # c #395987",
".# c #214579",
"+# c #294C7E",
"@# c #315383",
"## c #3D5C8A",
"$# c #43618D",
"%# c #5C769C",
"&# c #6D84A6",
"*# c #405E8B",
"=# c #375179",
"-# c #1B304F",
";# c #2E5181",
"># c #415F8C",
",# c #547098",
"'# c #486590",
")# c #43618E",
"!# c #476690",
"~# c #6981A4",
"{# c #677FA3",
"]# c #496690",
"^# c #2E5283",
"/# c #294467",
"(# c #32507C",
"_# c #325684",
":# c #335484",
"<# c #3F5E8B",
"[# c #3E5F8D",
"}# c #345887",
"|# c #305383",
"1# c #2A4466",
" ",
" . + ",
" @ # ",
" $ % & * ",
" = - ; > , ' ",
" ) ! ~ { ] ^ / ( ",
" _ : < [ } | 1 2 3 ",
" 4 5 6 7 8 9 0 a b c ",
" d e f g h i j k l m n o ",
" p q r r s t u v w x y z A ",
" B C r D E F G H I J K L M N O ",
" P Q R S T U V W I X Y Z ` ...+. ",
" @.#.$.%.&.*.=.-.;.>.,.'.).V !.~.{.]. ",
" ^./.(._.:.<.[.}.|.1.2.3.4.5.6.7.8.9.0. ",
" a.b.c.d.e.f.g.h.i.i.j.k.l.m.n.o.p.q.r.s. ",
" t.u.F v.w.x.y.z.A.B.C.D.E.F.G.H.I.J.K.L.M. ",
" N.c.O.P.Q.R.S.T.U.V.W.A.X.Y.Z.`. +.+++@+#+ ",
" $+%+&+*+=+-+;+>+,+'+)+!+~+i.|.{+]+^+++/+(+_+ ",
" :+<+r G [+}+|+1+2+3+4+5+T.6+7+F.8+]+9+K.0+a+b+ ",
" c+d+r e+f+g+h+i+j+k+l+m+)+n+o+l.p+q+r+s+t+u+v+ ",
" w+Q r x+y+z+A+B+C+D+E+F+G+H+I+|.J+K+L+M+N+O+P+ ",
" Q+R+r r S+T+*+U+V+W+X+F+Y+A.j.2.Z+`+ @.@+@@@#@ ",
" $@%@r &@*@=@-@;@>@,@,+'@)@!@~@{@]@^@/@(@_@:@ ",
" <@[@r r }@|@1@2@3@4@5@6@7@8@9@0@a@b@c@d@e@f@ ",
" g@b.h@r r i@j@k@l@m@n@o@p@q@r@s@t@u@v@w@x@ ",
" y@z@r r r %@y@A@B@C@D@E@F@G@H@I@J@K@L@M@ ",
" N@O@P@Q@r r r s e+R@S@T@` U@V@W@X@Y@Z@ ",
" `@ #Q y@Q@r r .#+#@#[@##$#%#&#*#=# ",
" -#;#>#d+,#'#)#!#u.Q ~#{#]#^#/# ",
" (#_#:# #<#$#[#}#|#1# ",
" ",
" "};

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

173
deluge/data/pixmaps/dht.svg Normal file
View File

@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docbase="/home/andrew"
sodipodi:docname="dht.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/dht16.png"
inkscape:export-xdpi="1.9579109"
inkscape:export-ydpi="1.9579109">
<defs
id="defs4">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10561" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="217.74745"
inkscape:cy="658.24369"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:window-width="1024"
inkscape:window-height="711"
inkscape:window-x="1280"
inkscape:window-y="0"
showgrid="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="arc"
style="opacity:1;fill:#8dd35f;fill-opacity:1;stroke:#000000;stroke-width:2.48568825;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3437"
sodipodi:cx="325.71429"
sodipodi:cy="215.21933"
sodipodi:rx="85.714287"
sodipodi:ry="85.714287"
d="M 411.42858,215.21933 A 85.714287,85.714287 0 1 1 240.00001,215.21933 A 85.714287,85.714287 0 1 1 411.42858,215.21933 z"
transform="matrix(1.6800176,0,0,1.6800176,-275.15013,-73.06899)"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922" />
<path
sodipodi:type="arc"
style="opacity:1;fill:#5599ff;fill-opacity:1;stroke:#000000;stroke-width:2.81711386;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3439"
sodipodi:cx="325.71429"
sodipodi:cy="215.21933"
sodipodi:rx="85.714287"
sodipodi:ry="85.714287"
d="M 411.42858,215.21933 A 85.714287,85.714287 0 1 1 240.00001,215.21933 A 85.714287,85.714287 0 1 1 411.42858,215.21933 z"
transform="matrix(1.4823682,0,0,1.4823682,82.877111,311.11956)"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922" />
<path
sodipodi:type="arc"
style="opacity:1;fill:#8dd35f;fill-opacity:1;stroke:#000000;stroke-width:3.2505155;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3441"
sodipodi:cx="325.71429"
sodipodi:cy="215.21933"
sodipodi:rx="85.714287"
sodipodi:ry="85.714287"
d="M 411.42858,215.21933 A 85.714287,85.714287 0 1 1 240.00001,215.21933 A 85.714287,85.714287 0 1 1 411.42858,215.21933 z"
transform="matrix(1.2847193,0,0,1.2847193,-104.04248,489.18828)"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922" />
<path
sodipodi:type="arc"
style="opacity:1;fill:#5599ff;fill-opacity:1;stroke:#000000;stroke-width:3.29273035;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3443"
sodipodi:cx="325.71429"
sodipodi:cy="215.21933"
sodipodi:rx="85.714287"
sodipodi:ry="85.714287"
d="M 411.42858,215.21933 A 85.714287,85.714287 0 1 1 240.00001,215.21933 A 85.714287,85.714287 0 1 1 411.42858,215.21933 z"
transform="matrix(1.2682484,0,0,1.2682484,-282.25061,288.22576)"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922" />
<path
sodipodi:type="arc"
style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:#000000;stroke-width:4.08935796;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path3445"
sodipodi:cx="325.71429"
sodipodi:cy="215.21933"
sodipodi:rx="85.714287"
sodipodi:ry="85.714287"
d="M 411.42858,215.21933 A 85.714287,85.714287 0 1 1 240.00001,215.21933 A 85.714287,85.714287 0 1 1 411.42858,215.21933 z"
transform="matrix(1.0211872,0,0,1.0211872,244.38469,139.31302)"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922" />
<path
style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10.04848194;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 409.97291,329.4348 L 487.75261,344.44338"
id="path3691"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922"
sodipodi:nodetypes="cc" />
<path
style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:8.96934509;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 423.06163,747.83054 L 464.53048,708.21583"
id="path3693"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922"
sodipodi:nodetypes="cc" />
<path
style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:11.37722969;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 315.62445,431.58184 L 310.19863,651.84005"
id="path3695"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922"
sodipodi:nodetypes="cc" />
<path
style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10.04848194;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 143.15025,452.25537 L 171.01599,395.38786"
id="path3697"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922"
sodipodi:nodetypes="cc" />
<path
style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:11.37722969;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 360.45007,405.53416 L 474.8177,539.94039"
id="path3699"
inkscape:export-filename="/home/andrew/dht16-2.png"
inkscape:export-xdpi="3.8918922"
inkscape:export-ydpi="3.8918922"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg3535"
sodipodi:version="0.32"
inkscape:version="0.46"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/downloading16.png"
inkscape:export-xdpi="1.7075963"
inkscape:export-ydpi="1.7075963"
sodipodi:docname="downloading.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs3537">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective3543" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="183.87749"
inkscape:cy="492.74541"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="686"
inkscape:window-height="711"
inkscape:window-x="1941"
inkscape:window-y="99" />
<metadata
id="metadata3540">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#8dd35f;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.07523891000000001px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 337.34641,113.574 L 612.77782,444.9064 C 811.69255,666.55556 698.49522,935.996 395.596,956.6026 C -30.5748,967.74548 -7.5325,620.6669 110.06775,461.45596 L 337.34641,113.574 z"
id="path2069"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:21.67123985;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.36612022"
d="M 339.91139,163.01784 L 587.46485,461.81598 C 776.20058,681.7222 655.61789,907.17006 386.12406,928.1221 C 6.36766,937.82375 31.27865,636.36028 127.47397,487.30722 L 339.91139,163.01784 z"
id="path2071"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417" />
<path
style="fill:#b7e399;fill-opacity:1;stroke:#000000;stroke-width:14.10382843;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 362.09611,847.31541 C 292.72038,779.25618 223.06258,711.37264 153.68685,643.3136 C 191.01405,643.2453 228.43453,643.3817 265.76192,643.3136 C 265.76192,562.7203 265.76192,482.12699 265.76192,401.53369 C 332.08337,401.53369 398.40502,401.53369 464.72665,401.53369 C 464.72665,481.91704 464.72665,562.30041 464.72665,642.68397 C 499.11804,642.62091 533.59544,642.74683 567.98683,642.68397 C 499.37714,710.99547 430.70559,779.00372 362.09611,847.31541 z"
id="rect2634"
inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/seeding16.png"
inkscape:export-xdpi="1.7060417"
inkscape:export-ydpi="1.7060417" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

View File

@ -0,0 +1,2 @@
From: http://famfamfam.com/lab/icons/flags/
"These flag icons are available for free use for any purpose with no requirement for attribution."

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Some files were not shown because too many files have changed in this diff Show More