Compare commits
130 Commits
deluge-2.0
...
deluge-2.1
Author | SHA1 | Date | |
---|---|---|---|
897955f0a1 | |||
ff309ea4c5 | |||
3b11613cc7 | |||
a2d0cb7141 | |||
88ffd1b843 | |||
6a10e57f7e | |||
612e0061ed | |||
2eee7453cb | |||
58cc278145 | |||
a03e649da6 | |||
073bbbc09d | |||
bca0aa3532 | |||
cb588d0205 | |||
2b20e9689b | |||
65f7cf0d83 | |||
5ac8f4c81b | |||
c33c9082d9 | |||
62ae0f5ef6 | |||
24aa48187e | |||
342cca4367 | |||
9194092d7b | |||
5f6f65a065 | |||
967537a409 | |||
f74163489c | |||
7a110bd60f | |||
d56636426e | |||
de4fbd2e82 | |||
9c3982d4ff | |||
88fc21e993 | |||
54674576db | |||
89189adb24 | |||
a5a7da4a1a | |||
1e6cc03946 | |||
d8526ba653 | |||
c38f913948 | |||
0659fe4641 | |||
10501db63d | |||
2a312159b9 | |||
cb75192df4 | |||
588f600ba2 | |||
ea609cd3e0 | |||
4b6c7d01b2 | |||
b89b2c45b1 | |||
e38f1173cf | |||
e1e0999de6 | |||
5c9378ac5e | |||
f075f391cb | |||
8fb25f71f3 | |||
a3332079db | |||
0d6eec7a33 | |||
f16afc59ba | |||
e5388048a9 | |||
5374d237a7 | |||
2e466101fc | |||
8676a0d2a0 | |||
3ec23ad96b | |||
dcd3918f36 | |||
08c7f1960f | |||
8a4ec493c0 | |||
4d970754a4 | |||
f331b6c754 | |||
1022448e4f | |||
291540b601 | |||
092d07b68e | |||
da5d5bee20 | |||
6d9dc9bd42 | |||
937afd921c | |||
a4da8d29f8 | |||
8ec5ca9d08 | |||
9c90136f57 | |||
610a1bb313 | |||
23a48dd01c | |||
d02fa72e80 | |||
62d8749e74 | |||
76f0bf2e04 | |||
635f6d970d | |||
672e3c42a8 | |||
c1110e4ef3 | |||
742c8a941a | |||
3427ae4b90 | |||
034db27936 | |||
1e3c624613 | |||
3519f341d4 | |||
d6c96d6291 | |||
15c250e152 | |||
eb57412601 | |||
2f1c008a26 | |||
5e06aee5c8 | |||
351664ec07 | |||
5f1eada3ea | |||
bde4e4443e | |||
ed4bc5fa17 | |||
20afc31f3c | |||
9232a52fd6 | |||
23b3f144fc | |||
89d62eb509 | |||
00176ee2cd | |||
8737005b82 | |||
d08c3f72e9 | |||
40ebdf3f39 | |||
eeeb7fb69b | |||
3f9ae33793 | |||
0c7f53e305 | |||
63a4301a8b | |||
1b4ac88ce7 | |||
4b29436cd5 | |||
833b5a1f30 | |||
24b094a04a | |||
3365201011 | |||
c1ba403d4e | |||
8b62e50eb8 | |||
5b315e90c5 | |||
b711cd258a | |||
e1c4069a72 | |||
a2dee79439 | |||
7a54db3179 | |||
03e7952d26 | |||
7ee8750be4 | |||
f61001a15d | |||
86ddadacf7 | |||
632089940c | |||
5d7db3e727 | |||
4dd1f63b8b | |||
fc134cdffb | |||
36cb4c5a4f | |||
676bdb26e0 | |||
dff778ceeb | |||
bdadd2b515 | |||
a34543100c | |||
b8b044f451 |
116
.github/workflows/ci.yml
vendored
Normal file
116
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-linux:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
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: "3.8"
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
# Look to see if there is a cache hit for the corresponding requirements file
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('tox.ini', 'setup.py', 'requirements*.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Add libtorrent deb repository
|
||||
uses: myci-actions/add-deb-repo@8
|
||||
with:
|
||||
repo: deb http://ppa.launchpad.net/libtorrent.org/1.2-daily/ubuntu focal main
|
||||
repo-name: libtorrent
|
||||
keys: 58E5430D9667FAEFFCA0B93F32309D6B9E009EDB
|
||||
key-server: keyserver.ubuntu.com
|
||||
install: python3-libtorrent-dbg
|
||||
|
||||
- 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
|
||||
cp /usr/lib/python3/dist-packages/libtorrent*.so $GITHUB_WORKSPACE/deluge
|
||||
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-latest
|
||||
|
||||
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: "3.7"
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '%LOCALAPPDATA%\pip\Cache'
|
||||
# Look to see if there is a cache hit for the corresponding requirements file
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('tox.ini', 'setup.py', 'requirements*.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip wheel
|
||||
python -m pip install libtorrent==1.2.*
|
||||
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 -m "not (todo or gtkui or security)" deluge
|
45
.github/workflows/docs.yml
vendored
Normal file
45
.github/workflows/docs.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
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
|
||||
|
||||
- name: Test with tox
|
||||
env:
|
||||
TOX_ENV: docs
|
||||
run: |
|
||||
tox -e $TOX_ENV
|
17
.github/workflows/lint.yml
vendored
Normal file
17
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
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
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,3 +22,5 @@ deluge/ui/web/js/extjs/ext-extensions*.js
|
||||
osx/app
|
||||
RELEASE-VERSION
|
||||
.venv*
|
||||
# used by setuptools to cache downloaded eggs
|
||||
/.eggs
|
||||
|
@ -1,4 +1,4 @@
|
||||
default_language:
|
||||
default_language_version:
|
||||
python: python3
|
||||
exclude: >
|
||||
(?x)^(
|
||||
@ -6,28 +6,29 @@ exclude: >
|
||||
)$
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 19.3b0
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: black
|
||||
name: Fmt Black
|
||||
language_version: python3.6
|
||||
- repo: https://github.com/prettier/prettier
|
||||
rev: 1.17.0
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v2.2.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
name: Fmt Prettier
|
||||
# Workaround to list modified files only.
|
||||
args: [--list-different]
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.7
|
||||
# v3.7.9 due to E402 issue: https://gitlab.com/pycqa/flake8/-/issues/638
|
||||
rev: 3.7.9
|
||||
hooks:
|
||||
- id: flake8
|
||||
name: Chk Flake8
|
||||
additional_dependencies:
|
||||
- flake8-isort==2.7
|
||||
- pep8-naming==0.8.2
|
||||
- flake8-isort==4.0.0
|
||||
- pep8-naming==0.11.1
|
||||
args: [--isort-show-traceback]
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.2.1
|
||||
rev: v3.4.0
|
||||
hooks:
|
||||
- id: double-quote-string-fixer
|
||||
name: Fix Double-quotes
|
||||
|
@ -289,7 +289,7 @@ callbacks=cb_,_cb
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six.moves,future.builtins,future_builtins
|
||||
redefining-builtins-modules=
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
@ -359,11 +359,6 @@ known-standard-library=
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
|
||||
[DESIGN]
|
||||
|
||||
|
83
.travis.yml
83
.travis.yml
@ -1,83 +0,0 @@
|
||||
dist: xenial
|
||||
sudo: required
|
||||
|
||||
language: python
|
||||
python:
|
||||
# Travis Xenial Python to support system_site_packages
|
||||
- 3.5
|
||||
cache: pip
|
||||
virtualenv:
|
||||
system_site_packages: true
|
||||
|
||||
env:
|
||||
global:
|
||||
- DISPLAY=:99.0
|
||||
|
||||
git:
|
||||
# Set greater depth to get version from tags.
|
||||
depth: 1000
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- name: Unit tests
|
||||
env: TOX_ENV=py3
|
||||
- name: Unit tests - libtorrent 1.2
|
||||
env: TOX_ENV=py3
|
||||
addons:
|
||||
apt:
|
||||
sources: [sourceline: "ppa:libtorrent.org/1.2-daily"]
|
||||
packages: [python3-libtorrent, python3-venv]
|
||||
- name: Unit tests - Python 2
|
||||
env: TOX_ENV=py27
|
||||
python: 2.7
|
||||
- if: commit_message =~ SECURITY_TEST
|
||||
env: TOX_ENV=security
|
||||
- name: Code linting
|
||||
env: TOX_ENV=lint
|
||||
- name: Docs build
|
||||
env: TOX_ENV=docs
|
||||
- name: GTK unit tests
|
||||
env: TOX_ENV=gtkui
|
||||
- name: Plugins unit tests
|
||||
env: TOX_ENV=plugins
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "ppa:libtorrent.org/rc-1.1-daily"
|
||||
- deadsnakes
|
||||
packages:
|
||||
- python-libtorrent
|
||||
- python3-libtorrent
|
||||
# Install py36 specifically for pre-commit to run black formatter.
|
||||
- python3.6
|
||||
# Intall python3-venv to provide ensurepip module for tox.
|
||||
- python3-venv
|
||||
|
||||
# Install dependencies
|
||||
install:
|
||||
- pip install tox tox-venv
|
||||
# GTKUI tests
|
||||
- "if [ $TOX_ENV == 'gtkui' ]; then
|
||||
sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0;
|
||||
fi"
|
||||
# Security tests
|
||||
- "if [ $TOX_ENV == 'security' ]; then
|
||||
testssl_url=https://github.com/drwetter/testssl.sh/archive/v2.9.5-5.tar.gz;
|
||||
wget -O- $testssl_url | tar xz
|
||||
&& mv -t deluge/tests/data testssl.sh-2.9.5-5/testssl.sh testssl.sh-2.9.5-5/etc/;
|
||||
fi"
|
||||
|
||||
before_script:
|
||||
- export PYTHONPATH=$PYTHONPATH:$PWD
|
||||
# Verify libtorrent installed and version
|
||||
- python -c "import libtorrent as lt; print(lt.__version__)"
|
||||
# Start xvfb for the GTKUI tests
|
||||
- "if [ $TOX_ENV == 'gtkui' ]; then
|
||||
/sbin/start-stop-daemon --start --quiet --background \
|
||||
--make-pidfile --pidfile /tmp/custom_xvfb_99.pid \
|
||||
--exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16;
|
||||
fi"
|
||||
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
17
AUTHORS
17
AUTHORS
@ -39,14 +39,9 @@ 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
|
||||
exceptions: deluge/ui/data/pixmaps/deluge.svg and derivatives
|
||||
copyright: Andrew Resch
|
||||
license: GPLv3
|
||||
|
||||
* files: deluge/ui/data/pixmaps/deluge.svg and derivatives
|
||||
deluge/ui/web/icons/apple-pre-*.png, deluge*.png
|
||||
deluge/ui/web/images/deluge*.png
|
||||
copyright: Andrew Wedderburn
|
||||
deluge/ui/web/icons/apple-pre-*.png, deluge*.png
|
||||
copyright: Calum Lind
|
||||
license: GPLv3
|
||||
|
||||
* files: deluge/plugins/blocklist/blocklist/data/*.png
|
||||
@ -55,11 +50,9 @@ Images Authors:
|
||||
license: GPLv2
|
||||
url: http://ftp.acc.umu.se/pub/GNOME/sources/gnome-icon-theme
|
||||
|
||||
* files: deluge/ui/data/pixmaps/magnet.png
|
||||
copyright: Woothemes
|
||||
license: Freeware
|
||||
icon pack: WP Woothemes Ultimate
|
||||
url: http://www.woothemes.com/
|
||||
* 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>
|
||||
|
130
CHANGELOG.md
130
CHANGELOG.md
@ -1,5 +1,109 @@
|
||||
# Changelog
|
||||
|
||||
## 2.1.0 (WIP)
|
||||
|
||||
- Removed Python 2 support.
|
||||
|
||||
## 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
|
||||
@ -8,13 +112,13 @@
|
||||
|
||||
### Core
|
||||
|
||||
- Fix Python 2 compatiblity issue with SimpleNamespace.
|
||||
- Fix Python 2 compatibility issue with SimpleNamespace.
|
||||
|
||||
## 2.0.1 (2019-06-07)
|
||||
|
||||
### Packaging
|
||||
|
||||
- Fix setup.py build error without git installed.
|
||||
- Fix `setup.py` build error without git installed.
|
||||
|
||||
## 2.0.0 (2019-06-06)
|
||||
|
||||
@ -32,37 +136,37 @@
|
||||
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 compatability.
|
||||
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 compatability.
|
||||
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 sould then ask the username/password to the user.
|
||||
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.
|
||||
|
||||
### GtkUI
|
||||
### 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.
|
||||
- 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
|
||||
- Up: `Ctrl+Alt+Up`
|
||||
- Down: `Ctrl+Alt+Down`
|
||||
- Top: `Ctrl+Alt+Shift+Up`
|
||||
- Bottom: `Ctrl+Alt+Shift+Down`
|
||||
|
||||
### WebUI
|
||||
### 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.
|
||||
@ -70,7 +174,7 @@
|
||||
### Blocklist Plugin
|
||||
|
||||
- Implemented whitelist support to both core and GTK UI.
|
||||
- Implemented ip filter cleaning before each update. Restarting the deluge
|
||||
- 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
|
||||
|
13
DEPENDS.md
13
DEPENDS.md
@ -13,7 +13,7 @@ All modules will require the [common](#common) section dependencies.
|
||||
|
||||
- [setuptools]
|
||||
- [intltool] - Optional: Desktop file translation for \*nix.
|
||||
- [closure-compiler] - Minify javascript (alternative is [slimit])
|
||||
- [closure-compiler] - Minify javascript (alternative is [rjsmin])
|
||||
|
||||
## Common
|
||||
|
||||
@ -23,18 +23,17 @@ All modules will require the [common](#common) section dependencies.
|
||||
- [rencode] _>= 1.0.2_ - Encoding library.
|
||||
- [PyXDG] - Access freedesktop.org standards for \*nix.
|
||||
- [xdg-utils] - Provides xdg-open for \*nix.
|
||||
- [six]
|
||||
- [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.
|
||||
|
||||
#### Linux and BSD
|
||||
### Linux and BSD
|
||||
|
||||
- [distro] - Optional: OS platform information.
|
||||
|
||||
#### Windows OS
|
||||
### Windows OS
|
||||
|
||||
- [pywin32]
|
||||
- [certifi]
|
||||
@ -52,7 +51,7 @@ All modules will require the [common](#common) section dependencies.
|
||||
- [librsvg] _>= 2_
|
||||
- [libappindicator3] w/GIR - Optional: Ubuntu system tray icon.
|
||||
|
||||
#### MacOS
|
||||
### MacOS
|
||||
|
||||
- [GtkOSXApplication]
|
||||
|
||||
@ -71,7 +70,7 @@ All modules will require the [common](#common) section dependencies.
|
||||
[setuptools]: https://setuptools.readthedocs.io/en/latest/
|
||||
[intltool]: https://freedesktop.org/wiki/Software/intltool/
|
||||
[closure-compiler]: https://developers.google.com/closure/compiler/
|
||||
[slimit]: https://slimit.readthedocs.io/en/latest/
|
||||
[rjsmin]: https://pypi.org/project/rjsmin/
|
||||
[openssl]: https://www.openssl.org/
|
||||
[pyopenssl]: https://pyopenssl.org
|
||||
[twisted]: https://twistedmatrix.com
|
||||
@ -81,14 +80,12 @@ All modules will require the [common](#common) section dependencies.
|
||||
[distro]: https://github.com/nir0s/distro
|
||||
[pywin32]: https://github.com/mhammond/pywin32
|
||||
[certifi]: https://pypi.org/project/certifi/
|
||||
[py2-ipaddress]: https://pypi.org/project/py2-ipaddress/
|
||||
[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/
|
||||
[six]: https://pythonhosted.org/six/
|
||||
[xdg-utils]: https://www.freedesktop.org/wiki/Software/xdg-utils/
|
||||
[gtk+]: https://www.gtk.org/
|
||||
[pycairo]: https://cairographics.org/pycairo/
|
||||
|
@ -23,7 +23,7 @@ recursive-exclude deluge/tests *.pyc
|
||||
|
||||
graft deluge/ui/data
|
||||
recursive-exclude deluge/ui/data *.desktop *.xml
|
||||
graft deluge/ui/gtkui/glade
|
||||
graft deluge/ui/gtk3/glade
|
||||
|
||||
include deluge/ui/web/index.html
|
||||
include deluge/ui/web/css/*.css
|
||||
|
25
README.md
25
README.md
@ -1,10 +1,10 @@
|
||||
# Deluge BitTorrent Client
|
||||
|
||||
[![build-status]][travis-deluge] [![docs-status]][rtd-deluge]
|
||||
[![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
|
||||
a Console-UI. It uses [libtorrent][lt] at it's core to handle the BitTorrent
|
||||
Console-UI. It uses [libtorrent][lt] at its core to handle the BitTorrent
|
||||
protocol.
|
||||
|
||||
## Install
|
||||
@ -13,10 +13,17 @@ From [PyPi](https://pypi.org/project/deluge):
|
||||
|
||||
pip install deluge
|
||||
|
||||
with all optional dependencies:
|
||||
|
||||
pip install deluge[all]
|
||||
|
||||
From source code:
|
||||
|
||||
python setup.py build
|
||||
python setup.py install
|
||||
pip install .
|
||||
|
||||
with all optional dependencies:
|
||||
|
||||
pip install .[all]
|
||||
|
||||
See [DEPENDS](DEPENDS.md) and [Installing/Source] for dependency details.
|
||||
|
||||
@ -51,13 +58,13 @@ See the [Thinclient guide] to connect to the daemon from another computer.
|
||||
- [Homepage](https://deluge-torrent.org)
|
||||
- [User guide][user guide]
|
||||
- [Forum](https://forum.deluge-torrent.org)
|
||||
- [IRC Freenode #deluge](irc://irc.freenode.net/deluge)
|
||||
- [IRC Libera.Chat #deluge](irc://irc.libera.chat/deluge)
|
||||
|
||||
[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://travis-ci.org/deluge-torrent/deluge.svg "Travis Status"
|
||||
[travis-deluge]: https://travis-ci.org/deluge-torrent/deluge
|
||||
[docs-status]: https://readthedocs.org/projects/deluge/badge/?version=develop
|
||||
[rtd-deluge]: https://deluge.readthedocs.io/en/develop/?badge=develop "Documentation Status"
|
||||
[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
|
||||
|
53
appveyor.yml
53
appveyor.yml
@ -1,53 +0,0 @@
|
||||
environment:
|
||||
PYTHON_VERSION: 3.6
|
||||
PYTHON_ARCH: 64
|
||||
PYTHON: "C:\\Python36-x64"
|
||||
APPVEYOR_SAVE_CACHE_ON_ERROR: true
|
||||
|
||||
matrix:
|
||||
- TOXENV: py36
|
||||
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
|
||||
install:
|
||||
# If there is a newer build queued for same PR, cancel this one. Credit: JuliaLang devs
|
||||
- ps:
|
||||
if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
|
||||
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
|
||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
||||
throw "There are newer queued builds for this pull request, failing early." }
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
- "python -VV"
|
||||
- if defined TOXENV (
|
||||
python -m pip install tox tox_venv
|
||||
) else (
|
||||
python -m pip install -rrequirements.txt pygame bbfreeze pefile
|
||||
)
|
||||
- "SET PATH=C:\\OpenSSL-v11-Win64\\bin;%PATH%"
|
||||
- openssl version -v
|
||||
- python -m pip install deluge-libtorrent
|
||||
- 'python -c "import libtorrent; print(libtorrent.__version__)"'
|
||||
|
||||
cache:
|
||||
- '%LOCALAPPDATA%\pip\cache'
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- if defined TOXENV tox
|
||||
# Commented out as require GTK3 to create package.
|
||||
# after_test:
|
||||
# - if not defined TOXENV python setup.py build && python setup.py install
|
||||
# - cd %APPVEYOR_BUILD_FOLDER%\\packaging\\win32
|
||||
# - if not defined TOXENV deluge-bbfreeze.py debug
|
||||
# - "SET PATH=C:\\Program Files (x86)\\NSIS;%PATH%"
|
||||
# - if not defined TOXENV makensis deluge-win32-installer.nsi
|
||||
# - if not defined TOXENV 7z a deluge-win32.zip build-win32 "-x!*.exe"
|
||||
|
||||
# artifacts:
|
||||
# - path: packaging\win32\deluge-win32.zip
|
||||
# - path: packaging\win32\build-win32\*.exe
|
||||
|
||||
#on_success:
|
||||
#
|
@ -15,19 +15,22 @@ Example:
|
||||
>>> from deluge._libtorrent import lt
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from deluge.common import VersionSplit, get_version
|
||||
from deluge.error import LibtorrentImportError
|
||||
|
||||
try:
|
||||
import deluge.libtorrent as lt
|
||||
except ImportError:
|
||||
import libtorrent as lt
|
||||
try:
|
||||
import libtorrent as lt
|
||||
except ImportError as ex:
|
||||
raise LibtorrentImportError('No libtorrent library found: %s' % (ex))
|
||||
|
||||
|
||||
REQUIRED_VERSION = '1.1.2.0'
|
||||
LT_VERSION = lt.__version__
|
||||
|
||||
if VersionSplit(LT_VERSION) < VersionSplit(REQUIRED_VERSION):
|
||||
raise ImportError(
|
||||
raise LibtorrentImportError(
|
||||
'Deluge %s requires libtorrent >= %s' % (get_version(), REQUIRED_VERSION)
|
||||
)
|
||||
|
@ -7,8 +7,6 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
@ -122,7 +120,7 @@ class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
|
||||
|
||||
"""
|
||||
if not action.option_strings:
|
||||
metavar, = self._metavar_formatter(action, action.dest)(1)
|
||||
(metavar,) = self._metavar_formatter(action, action.dest)(1)
|
||||
return metavar
|
||||
else:
|
||||
parts = []
|
||||
@ -264,7 +262,7 @@ class ArgParserBase(argparse.ArgumentParser):
|
||||
args = [a for a in args if a not in withhold]
|
||||
options, remaining = super(ArgParserBase, self).parse_known_args(args=args)
|
||||
options.remaining = remaining
|
||||
# Hanlde common and process group options
|
||||
# Handle common and process group options
|
||||
return self._handle_ui_options(options)
|
||||
|
||||
def _handle_ui_options(self, options):
|
||||
@ -325,22 +323,22 @@ class ArgParserBase(argparse.ArgumentParser):
|
||||
|
||||
# Write pid file before chuid
|
||||
if options.pidfile:
|
||||
with open(options.pidfile, 'wb') as _file:
|
||||
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)
|
||||
if options.group:
|
||||
if not options.group.isdigit():
|
||||
import grp
|
||||
|
||||
options.group = grp.getgrnam(options.group)[2]
|
||||
os.setuid(options.group)
|
||||
|
||||
return options
|
||||
|
||||
|
@ -9,13 +9,7 @@
|
||||
# License.
|
||||
|
||||
# Written by Petru Paler
|
||||
# Updated by Calum Lind to support both Python 2 and Python 3.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from sys import version_info
|
||||
|
||||
PY2 = version_info.major == 2
|
||||
# Updated by Calum Lind to support Python 3.
|
||||
|
||||
|
||||
class BTFailure(Exception):
|
||||
@ -146,10 +140,6 @@ encode_func[dict] = encode_dict
|
||||
encode_func[bool] = encode_bool
|
||||
encode_func[str] = encode_string
|
||||
encode_func[bytes] = encode_bytes
|
||||
if PY2:
|
||||
encode_func[long] = encode_int # noqa: F821
|
||||
encode_func[str] = encode_bytes
|
||||
encode_func[unicode] = encode_string # noqa: F821
|
||||
|
||||
|
||||
def bencode(x):
|
||||
|
138
deluge/common.py
138
deluge/common.py
@ -8,8 +8,6 @@
|
||||
#
|
||||
|
||||
"""Common functions for various parts of Deluge to use."""
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import functools
|
||||
@ -27,6 +25,8 @@ import time
|
||||
from contextlib import closing
|
||||
from datetime import datetime
|
||||
from io import BytesIO, open
|
||||
from urllib.parse import unquote_plus, urljoin
|
||||
from urllib.request import pathname2url
|
||||
|
||||
import pkg_resources
|
||||
|
||||
@ -38,14 +38,6 @@ try:
|
||||
except ImportError:
|
||||
chardet = None
|
||||
|
||||
try:
|
||||
from urllib.parse import unquote_plus, urljoin
|
||||
from urllib.request import pathname2url
|
||||
except ImportError:
|
||||
# PY2 fallback
|
||||
from urlparse import urljoin # pylint: disable=ungrouped-imports
|
||||
from urllib import pathname2url, unquote_plus # pylint: disable=ungrouped-imports
|
||||
|
||||
# Windows workaround for HTTPS requests requiring certificate authority bundle.
|
||||
# see: https://twistedmatrix.com/trac/ticket/9209
|
||||
if platform.system() in ('Windows', 'Microsoft'):
|
||||
@ -81,7 +73,8 @@ TORRENT_STATE = [
|
||||
# The output formatting for json.dump
|
||||
JSON_FORMAT = {'indent': 4, 'sort_keys': True, 'ensure_ascii': False}
|
||||
|
||||
PY2 = sys.version_info.major == 2
|
||||
DBUS_FM_ID = 'org.freedesktop.FileManager1'
|
||||
DBUS_FM_PATH = '/org/freedesktop/FileManager1'
|
||||
|
||||
|
||||
def get_version():
|
||||
@ -108,10 +101,8 @@ def get_default_config_dir(filename=None):
|
||||
def save_config_path(resource):
|
||||
app_data_path = os.environ.get('APPDATA')
|
||||
if not app_data_path:
|
||||
try:
|
||||
import winreg
|
||||
except ImportError:
|
||||
import _winreg as winreg # For Python 2.
|
||||
import winreg
|
||||
|
||||
hkey = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders',
|
||||
@ -175,8 +166,8 @@ def archive_files(arc_name, filepaths, message=None, rotate=10):
|
||||
|
||||
from deluge.configmanager import get_config_dir
|
||||
|
||||
# Set archive compression to lzma with bz2 fallback.
|
||||
arc_comp = 'xz' if not PY2 else 'bz2'
|
||||
# Set archive compression to lzma
|
||||
arc_comp = 'xz'
|
||||
|
||||
archive_dir = os.path.join(get_config_dir(), 'archive')
|
||||
timestamp = datetime.now().replace(microsecond=0).isoformat().replace(':', '-')
|
||||
@ -355,27 +346,30 @@ def show_file(path, timestamp=None):
|
||||
timestamp,
|
||||
timestamp,
|
||||
)
|
||||
|
||||
if dbus:
|
||||
bus = dbus.SessionBus()
|
||||
filemanager1 = bus.get_object(
|
||||
'org.freedesktop.FileManager1', '/org/freedesktop/FileManager1'
|
||||
)
|
||||
paths = [urljoin('file:', pathname2url(path))]
|
||||
filemanager1.ShowItems(
|
||||
paths, startup_id, dbus_interface='org.freedesktop.FileManager1'
|
||||
)
|
||||
else:
|
||||
env = os.environ.copy()
|
||||
env['DESKTOP_STARTUP_ID'] = startup_id.replace('dbus', 'xdg-open')
|
||||
# No option in xdg to highlight a file so just open parent folder.
|
||||
subprocess.Popen(['xdg-open', os.path.dirname(path.rstrip('/'))], env=env)
|
||||
try:
|
||||
filemanager1 = bus.get_object(DBUS_FM_ID, DBUS_FM_PATH)
|
||||
except dbus.exceptions.DBusException as ex:
|
||||
log.debug('Unable to get dbus file manager: %s', ex)
|
||||
# Fallback to xdg-open
|
||||
else:
|
||||
paths = [urljoin('file:', pathname2url(path))]
|
||||
filemanager1.ShowItems(paths, startup_id, dbus_interface=DBUS_FM_ID)
|
||||
return
|
||||
|
||||
env = os.environ.copy()
|
||||
env['DESKTOP_STARTUP_ID'] = startup_id.replace('dbus', 'xdg-open')
|
||||
# No option in xdg to highlight a file so just open parent folder.
|
||||
subprocess.Popen(['xdg-open', os.path.dirname(path.rstrip('/'))], env=env)
|
||||
|
||||
|
||||
def open_url_in_browser(url):
|
||||
"""
|
||||
Opens a url in the desktop's default browser
|
||||
Opens a URL in the desktop's default browser
|
||||
|
||||
:param url: the url to open
|
||||
:param url: the URL to open
|
||||
:type url: string
|
||||
|
||||
"""
|
||||
@ -430,7 +424,7 @@ def fsize(fsize_b, precision=1, shortform=False):
|
||||
'110 KiB'
|
||||
|
||||
Note:
|
||||
This function has been refactored for perfomance with the
|
||||
This function has been refactored for performance with the
|
||||
fsize units being translated outside the function.
|
||||
|
||||
"""
|
||||
@ -565,7 +559,7 @@ def ftime(secs):
|
||||
'6h 23m'
|
||||
|
||||
Note:
|
||||
This function has been refactored for perfomance.
|
||||
This function has been refactored for performance.
|
||||
|
||||
"""
|
||||
|
||||
@ -695,7 +689,7 @@ def is_url(url):
|
||||
"""
|
||||
A simple test to check if the URL is valid
|
||||
|
||||
:param url: the url to test
|
||||
:param url: the URL to test
|
||||
:type url: string
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
@ -731,9 +725,9 @@ TR_PARAM = 'tr='
|
||||
|
||||
def is_magnet(uri):
|
||||
"""
|
||||
A check to determine if a uri is a valid bittorrent magnet uri
|
||||
A check to determine if a URI is a valid bittorrent magnet URI
|
||||
|
||||
:param uri: the uri to check
|
||||
:param uri: the URI to check
|
||||
:type uri: string
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
@ -819,7 +813,7 @@ def get_magnet_info(uri):
|
||||
|
||||
|
||||
def create_magnet_uri(infohash, name=None, trackers=None):
|
||||
"""Creates a magnet uri
|
||||
"""Creates a magnet URI
|
||||
|
||||
Args:
|
||||
infohash (str): The info-hash of the torrent.
|
||||
@ -827,7 +821,7 @@ def create_magnet_uri(infohash, name=None, trackers=None):
|
||||
trackers (list or dict, optional): A list of trackers or dict or {tracker: tier} pairs.
|
||||
|
||||
Returns:
|
||||
str: A magnet uri string.
|
||||
str: A magnet URI string.
|
||||
|
||||
"""
|
||||
try:
|
||||
@ -1007,9 +1001,9 @@ def decode_bytes(byte_str, encoding='utf8'):
|
||||
if encoding.lower() not in ['utf8', 'utf-8']:
|
||||
encodings.insert(0, lambda: (encoding, 'strict'))
|
||||
|
||||
for l in encodings:
|
||||
for enc in encodings:
|
||||
try:
|
||||
return byte_str.decode(*l())
|
||||
return byte_str.decode(*enc())
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return ''
|
||||
@ -1138,6 +1132,7 @@ AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
|
||||
|
||||
def create_auth_file():
|
||||
import stat
|
||||
|
||||
import deluge.configmanager
|
||||
|
||||
auth_file = deluge.configmanager.get_config_dir('auth')
|
||||
@ -1153,6 +1148,7 @@ def create_auth_file():
|
||||
def create_localclient_account(append=False):
|
||||
import random
|
||||
from hashlib import sha1 as sha
|
||||
|
||||
import deluge.configmanager
|
||||
|
||||
auth_file = deluge.configmanager.get_config_dir('auth')
|
||||
@ -1175,7 +1171,7 @@ def create_localclient_account(append=False):
|
||||
|
||||
|
||||
def get_localhost_auth():
|
||||
"""Grabs the localclient auth line from the 'auth' file and creates a localhost uri.
|
||||
"""Grabs the localclient auth line from the 'auth' file and creates a localhost URI.
|
||||
|
||||
Returns:
|
||||
tuple: With the username and password to login as.
|
||||
@ -1231,15 +1227,10 @@ def set_env_variable(name, value):
|
||||
http://sourceforge.net/p/gramps/code/HEAD/tree/branches/maintenance/gramps32/src/TransUtils.py
|
||||
"""
|
||||
# Update Python's copy of the environment variables
|
||||
try:
|
||||
os.environ[name] = value
|
||||
except UnicodeEncodeError:
|
||||
# Python 2
|
||||
os.environ[name] = value.encode('utf8')
|
||||
os.environ[name] = value
|
||||
|
||||
if windows_check():
|
||||
from ctypes import windll
|
||||
from ctypes import cdll
|
||||
from ctypes import cdll, windll
|
||||
|
||||
# Update the copy maintained by Windows (so SysInternals Process Explorer sees it)
|
||||
result = windll.kernel32.SetEnvironmentVariableW(name, value)
|
||||
@ -1264,45 +1255,22 @@ def set_env_variable(name, value):
|
||||
|
||||
def unicode_argv():
|
||||
""" Gets sys.argv as list of unicode objects on any platform."""
|
||||
if windows_check():
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'.
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
# On platforms other than Windows, we have to find the likely encoding of the args and decode
|
||||
# First check if sys.stdout or stdin have encoding set
|
||||
encoding = getattr(sys.stdout, 'encoding') or getattr(sys.stdin, 'encoding')
|
||||
# If that fails, check what the locale is set to
|
||||
encoding = encoding or locale.getpreferredencoding()
|
||||
# As a last resort, just default to utf-8
|
||||
encoding = encoding or 'utf-8'
|
||||
|
||||
get_cmd_linew = cdll.kernel32.GetCommandLineW
|
||||
get_cmd_linew.argtypes = []
|
||||
get_cmd_linew.restype = LPCWSTR
|
||||
arg_list = []
|
||||
for arg in sys.argv:
|
||||
try:
|
||||
arg_list.append(arg.decode(encoding))
|
||||
except AttributeError:
|
||||
arg_list.append(arg)
|
||||
|
||||
cmdline_to_argvw = windll.shell32.CommandLineToArgvW
|
||||
cmdline_to_argvw.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
cmdline_to_argvw.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = get_cmd_linew()
|
||||
argc = c_int(0)
|
||||
argv = cmdline_to_argvw(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in range(start, argc.value)]
|
||||
else:
|
||||
# On other platforms, we have to find the likely encoding of the args and decode
|
||||
# First check if sys.stdout or stdin have encoding set
|
||||
encoding = getattr(sys.stdout, 'encoding') or getattr(sys.stdin, 'encoding')
|
||||
# If that fails, check what the locale is set to
|
||||
encoding = encoding or locale.getpreferredencoding()
|
||||
# As a last resort, just default to utf-8
|
||||
encoding = encoding or 'utf-8'
|
||||
|
||||
arg_list = []
|
||||
for arg in sys.argv:
|
||||
try:
|
||||
arg_list.append(arg.decode(encoding))
|
||||
except AttributeError:
|
||||
arg_list.append(arg)
|
||||
|
||||
return arg_list
|
||||
return arg_list
|
||||
|
||||
|
||||
def run_profiled(func, *args, **kwargs):
|
||||
|
@ -7,13 +7,10 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
|
||||
from six import string_types
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import DeferredList, fail, maybeDeferred, succeed
|
||||
from twisted.internet.task import LoopingCall, deferLater
|
||||
@ -293,7 +290,8 @@ class ComponentRegistry(object):
|
||||
obj (Component): a component object to deregister
|
||||
|
||||
Returns:
|
||||
Deferred: a deferred object that will fire once the Component has been sucessfully deregistered
|
||||
Deferred: a deferred object that will fire once the Component has been
|
||||
successfully deregistered
|
||||
|
||||
"""
|
||||
if obj in self.components.values():
|
||||
@ -324,7 +322,7 @@ class ComponentRegistry(object):
|
||||
# Start all the components if names is empty
|
||||
if not names:
|
||||
names = list(self.components)
|
||||
elif isinstance(names, string_types):
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
def on_depends_started(result, name):
|
||||
@ -358,7 +356,7 @@ class ComponentRegistry(object):
|
||||
"""
|
||||
if not names:
|
||||
names = list(self.components)
|
||||
elif isinstance(names, string_types):
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
def on_dependents_stopped(result, name):
|
||||
@ -398,7 +396,7 @@ class ComponentRegistry(object):
|
||||
"""
|
||||
if not names:
|
||||
names = list(self.components)
|
||||
elif isinstance(names, string_types):
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
deferreds = []
|
||||
@ -424,7 +422,7 @@ class ComponentRegistry(object):
|
||||
"""
|
||||
if not names:
|
||||
names = list(self.components)
|
||||
elif isinstance(names, string_types):
|
||||
elif isinstance(names, str):
|
||||
names = [names]
|
||||
|
||||
deferreds = []
|
||||
@ -448,7 +446,7 @@ class ComponentRegistry(object):
|
||||
|
||||
def on_stopped(result):
|
||||
return DeferredList(
|
||||
[comp._component_shutdown() for comp in self.components.values()]
|
||||
[comp._component_shutdown() for comp in list(self.components.values())]
|
||||
)
|
||||
|
||||
return self.stop(list(self.components)).addCallback(on_stopped)
|
||||
|
@ -39,18 +39,15 @@ this can only be done for the 'config file version' and not for the 'format'
|
||||
version as this will be done internally.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
import shutil
|
||||
from codecs import getwriter
|
||||
from io import open
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
import six.moves.cPickle as pickle # noqa: N813
|
||||
|
||||
from deluge.common import JSON_FORMAT, get_default_config_dir
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -74,38 +71,33 @@ def prop(func):
|
||||
return property(doc=func.__doc__, **func())
|
||||
|
||||
|
||||
def find_json_objects(s):
|
||||
"""Find json objects in a string.
|
||||
def find_json_objects(text, decoder=json.JSONDecoder()):
|
||||
"""Find json objects in text.
|
||||
|
||||
Args:
|
||||
s (str): the string to find json objects in
|
||||
text (str): The text to find json objects within.
|
||||
|
||||
Returns:
|
||||
list: A list of tuples containing start and end locations of json
|
||||
objects in string `s`. e.g. [(start, end), ...]
|
||||
objects in the text. e.g. [(start, end), ...]
|
||||
|
||||
|
||||
"""
|
||||
objects = []
|
||||
opens = 0
|
||||
start = s.find('{')
|
||||
offset = start
|
||||
offset = 0
|
||||
while True:
|
||||
try:
|
||||
start = text.index('{', offset)
|
||||
except ValueError:
|
||||
break
|
||||
|
||||
if start < 0:
|
||||
return []
|
||||
|
||||
quoted = False
|
||||
for index, c in enumerate(s[offset:]):
|
||||
if c == '"':
|
||||
quoted = not quoted
|
||||
elif quoted:
|
||||
continue
|
||||
elif c == '{':
|
||||
opens += 1
|
||||
elif c == '}':
|
||||
opens -= 1
|
||||
if opens == 0:
|
||||
objects.append((start, index + offset + 1))
|
||||
start = index + offset + 1
|
||||
try:
|
||||
__, index = decoder.raw_decode(text[start:])
|
||||
except json.decoder.JSONDecodeError:
|
||||
offset = start + 1
|
||||
else:
|
||||
offset = start + index
|
||||
objects.append((start, offset))
|
||||
|
||||
return objects
|
||||
|
||||
@ -209,9 +201,9 @@ class Config(object):
|
||||
global callLater
|
||||
if callLater is None:
|
||||
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
|
||||
from twisted.internet.reactor import (
|
||||
from twisted.internet.reactor import ( # pylint: disable=redefined-outer-name
|
||||
callLater,
|
||||
) # pylint: disable=redefined-outer-name
|
||||
)
|
||||
# Run the set_function for this key if any
|
||||
try:
|
||||
for func in self.__set_functions[key]:
|
||||
@ -309,9 +301,9 @@ class Config(object):
|
||||
global callLater
|
||||
if callLater is None:
|
||||
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
|
||||
from twisted.internet.reactor import (
|
||||
from twisted.internet.reactor import ( # pylint: disable=redefined-outer-name
|
||||
callLater,
|
||||
) # pylint: disable=redefined-outer-name
|
||||
)
|
||||
|
||||
# We set the save_timer for 5 seconds if not already set
|
||||
if not self._save_timer or not self._save_timer.active():
|
||||
|
@ -7,8 +7,6 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
@ -15,10 +15,8 @@ This should typically only be used by the Core. Plugins should utilize the
|
||||
`:mod:EventManager` for similar functionality.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import types
|
||||
from types import SimpleNamespace
|
||||
|
||||
from twisted.internet import reactor
|
||||
|
||||
@ -28,14 +26,6 @@ from deluge.common import decode_bytes
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
SimpleNamespace = types.SimpleNamespace # Python 3.3+
|
||||
except AttributeError:
|
||||
|
||||
class SimpleNamespace(object): # Python 2.7
|
||||
def __init__(self, **attr):
|
||||
self.__dict__.update(attr)
|
||||
|
||||
|
||||
class AlertManager(component.Component):
|
||||
"""AlertManager fetches and processes libtorrent alerts"""
|
||||
|
@ -8,8 +8,6 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
@ -101,7 +99,7 @@ class AuthManager(component.Component):
|
||||
int: The auth level for this user.
|
||||
|
||||
Raises:
|
||||
AuthenticationRequired: If aditional details are required to authenticate.
|
||||
AuthenticationRequired: If additional details are required to authenticate.
|
||||
BadLoginError: If the username does not exist or password does not match.
|
||||
|
||||
"""
|
||||
|
@ -8,8 +8,6 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import division, unicode_literals
|
||||
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
@ -17,8 +15,8 @@ import shutil
|
||||
import tempfile
|
||||
import threading
|
||||
from base64 import b64decode, b64encode
|
||||
from urllib.request import URLError, urlopen
|
||||
|
||||
from six import string_types
|
||||
from twisted.internet import defer, reactor, task
|
||||
from twisted.web.client import Agent, readBody
|
||||
|
||||
@ -56,12 +54,6 @@ from deluge.event import (
|
||||
)
|
||||
from deluge.httpdownloader import download_file
|
||||
|
||||
try:
|
||||
from urllib.request import urlopen, URLError
|
||||
except ImportError:
|
||||
# PY2 fallback
|
||||
from urllib2 import urlopen, URLError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEPR_SESSION_STATUS_KEYS = {
|
||||
@ -267,7 +259,7 @@ class Core(component.Component):
|
||||
version (str): The version string in PEP440 dotted notation.
|
||||
|
||||
Returns:
|
||||
str: The formattted peer_id with Deluge prefix e.g. '--DE200s--'
|
||||
str: The formatted peer_id with Deluge prefix e.g. '--DE200s--'
|
||||
|
||||
"""
|
||||
split = deluge.common.VersionSplit(version)
|
||||
@ -358,8 +350,8 @@ class Core(component.Component):
|
||||
|
||||
if blocks_read:
|
||||
self.session_status['read_hit_ratio'] = (
|
||||
self.session_status['disk.num_blocks_cache_hits'] / blocks_read
|
||||
)
|
||||
blocks_read - self.session_status['disk.num_read_ops']
|
||||
) / blocks_read
|
||||
else:
|
||||
self.session_status['read_hit_ratio'] = 0.0
|
||||
|
||||
@ -405,7 +397,7 @@ class Core(component.Component):
|
||||
# Exported Methods
|
||||
@export
|
||||
def add_torrent_file_async(self, filename, filedump, options, save_state=True):
|
||||
"""Adds a torrent file to the session asynchonously.
|
||||
"""Adds a torrent file to the session asynchronously.
|
||||
|
||||
Args:
|
||||
filename (str): The filename of the torrent.
|
||||
@ -442,8 +434,8 @@ class Core(component.Component):
|
||||
Used by UIs to get magnet files for selection before adding to session.
|
||||
|
||||
Args:
|
||||
magnet (str): The magnet uri.
|
||||
timeout (int): Number of seconds to wait before cancelling request.
|
||||
magnet (str): The magnet URI.
|
||||
timeout (int): Number of seconds to wait before canceling request.
|
||||
|
||||
Returns:
|
||||
Deferred: A tuple of (torrent_id (str), metadata (dict)) for the magnet.
|
||||
@ -456,7 +448,7 @@ class Core(component.Component):
|
||||
return result
|
||||
|
||||
d = self.torrentmanager.prefetch_metadata(magnet, timeout)
|
||||
# Use a seperate callback chain to handle existing prefetching magnet.
|
||||
# Use a separate callback chain to handle existing prefetching magnet.
|
||||
result_d = defer.Deferred()
|
||||
d.addBoth(on_metadata, result_d)
|
||||
return result_d
|
||||
@ -488,10 +480,11 @@ class Core(component.Component):
|
||||
|
||||
@export
|
||||
def add_torrent_files(self, torrent_files):
|
||||
"""Adds multiple torrent files to the session asynchonously.
|
||||
"""Adds multiple torrent files to the session asynchronously.
|
||||
|
||||
Args:
|
||||
torrent_files (list of tuples): Torrent files as tuple of (filename, filedump, options).
|
||||
torrent_files (list of tuples): Torrent files as tuple of
|
||||
``(filename, filedump, options)``.
|
||||
|
||||
Returns:
|
||||
Deferred
|
||||
@ -517,10 +510,10 @@ class Core(component.Component):
|
||||
@export
|
||||
def add_torrent_url(self, url, options, headers=None):
|
||||
"""
|
||||
Adds a torrent from a url. Deluge will attempt to fetch the torrent
|
||||
from url prior to adding it to the session.
|
||||
Adds a torrent from a URL. Deluge will attempt to fetch the torrent
|
||||
from the URL prior to adding it to the session.
|
||||
|
||||
:param url: the url pointing to the torrent file
|
||||
:param url: the URL pointing to the torrent file
|
||||
:type url: string
|
||||
:param options: the options to apply to the torrent on add
|
||||
:type options: dict
|
||||
@ -529,7 +522,7 @@ class Core(component.Component):
|
||||
|
||||
:returns: a Deferred which returns the torrent_id as a str or None
|
||||
"""
|
||||
log.info('Attempting to add url %s', url)
|
||||
log.info('Attempting to add URL %s', url)
|
||||
|
||||
def on_download_success(filename):
|
||||
# We got the file, so add it to the session
|
||||
@ -543,7 +536,7 @@ class Core(component.Component):
|
||||
|
||||
def on_download_fail(failure):
|
||||
# Log the error and pass the failure onto the client
|
||||
log.error('Failed to add torrent from url %s', url)
|
||||
log.error('Failed to add torrent from URL %s', url)
|
||||
return failure
|
||||
|
||||
tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent')
|
||||
@ -566,7 +559,7 @@ class Core(component.Component):
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
log.debug('Attempting to add by magnet uri: %s', uri)
|
||||
log.debug('Attempting to add by magnet URI: %s', uri)
|
||||
|
||||
return self.torrentmanager.add(magnet=uri, options=options)
|
||||
|
||||
@ -652,7 +645,7 @@ class Core(component.Component):
|
||||
)
|
||||
status[key] = self.session_status[new_key]
|
||||
else:
|
||||
log.warning('Session status key not valid: %s', key)
|
||||
log.debug('Session status key not valid: %s', key)
|
||||
return status
|
||||
|
||||
@export
|
||||
@ -665,7 +658,7 @@ class Core(component.Component):
|
||||
def pause_torrent(self, torrent_id):
|
||||
"""Pauses a torrent"""
|
||||
log.debug('Pausing: %s', torrent_id)
|
||||
if not isinstance(torrent_id, string_types):
|
||||
if not isinstance(torrent_id, str):
|
||||
self.pause_torrents(torrent_id)
|
||||
else:
|
||||
self.torrentmanager[torrent_id].pause()
|
||||
@ -716,7 +709,7 @@ class Core(component.Component):
|
||||
def resume_torrent(self, torrent_id):
|
||||
"""Resumes a torrent"""
|
||||
log.debug('Resuming: %s', torrent_id)
|
||||
if not isinstance(torrent_id, string_types):
|
||||
if not isinstance(torrent_id, str):
|
||||
self.resume_torrents(torrent_id)
|
||||
else:
|
||||
self.torrentmanager[torrent_id].resume()
|
||||
@ -746,7 +739,7 @@ class Core(component.Component):
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
# Torrent was probaly removed meanwhile
|
||||
# Torrent was probably removed meanwhile
|
||||
return {}
|
||||
|
||||
# Ask the plugin manager to fill in the plugin keys
|
||||
@ -894,12 +887,13 @@ class Core(component.Component):
|
||||
|
||||
Args:
|
||||
torrent_ids (list): A list of torrent_ids to set the options for.
|
||||
options (dict): A dict of torrent options to set. See torrent.TorrentOptions class for valid keys.
|
||||
options (dict): A dict of torrent options to set. See
|
||||
``torrent.TorrentOptions`` class for valid keys.
|
||||
"""
|
||||
if 'owner' in options and not self.authmanager.has_account(options['owner']):
|
||||
raise DelugeError('Username "%s" is not known.' % options['owner'])
|
||||
|
||||
if isinstance(torrent_ids, string_types):
|
||||
if isinstance(torrent_ids, str):
|
||||
torrent_ids = [torrent_ids]
|
||||
|
||||
for torrent_id in torrent_ids:
|
||||
@ -907,9 +901,13 @@ class Core(component.Component):
|
||||
|
||||
@export
|
||||
def set_torrent_trackers(self, torrent_id, trackers):
|
||||
"""Sets a torrents tracker list. trackers will be [{"url", "tier"}]"""
|
||||
"""Sets a torrents tracker list. trackers will be ``[{"url", "tier"}]``"""
|
||||
return self.torrentmanager[torrent_id].set_trackers(trackers)
|
||||
|
||||
@export
|
||||
def get_magnet_uri(self, torrent_id):
|
||||
return self.torrentmanager[torrent_id].get_magnet_uri()
|
||||
|
||||
@deprecated
|
||||
@export
|
||||
def set_torrent_max_connections(self, torrent_id, value):
|
||||
@ -985,7 +983,7 @@ class Core(component.Component):
|
||||
@export
|
||||
def get_path_size(self, path):
|
||||
"""Returns the size of the file or folder 'path' and -1 if the path is
|
||||
unaccessible (non-existent or insufficient privs)"""
|
||||
inaccessible (non-existent or insufficient privileges)"""
|
||||
return deluge.common.get_path_size(path)
|
||||
|
||||
@export
|
||||
@ -1058,8 +1056,8 @@ class Core(component.Component):
|
||||
def upload_plugin(self, filename, filedump):
|
||||
"""This method is used to upload new plugins to the daemon. It is used
|
||||
when connecting to the daemon remotely and installing a new plugin on
|
||||
the client side. 'plugin_data' is a xmlrpc.Binary object of the file data,
|
||||
ie, plugin_file.read()"""
|
||||
the client side. ``plugin_data`` is a ``xmlrpc.Binary`` object of the file data,
|
||||
i.e. ``plugin_file.read()``"""
|
||||
|
||||
try:
|
||||
filedump = b64decode(filedump)
|
||||
@ -1075,14 +1073,14 @@ class Core(component.Component):
|
||||
@export
|
||||
def rescan_plugins(self):
|
||||
"""
|
||||
Rescans the plugin folders for new plugins
|
||||
Re-scans the plugin folders for new plugins
|
||||
"""
|
||||
component.get('CorePluginManager').scan_for_plugins()
|
||||
|
||||
@export
|
||||
def rename_files(self, torrent_id, filenames):
|
||||
"""
|
||||
Rename files in torrent_id. Since this is an asynchronous operation by
|
||||
Rename files in ``torrent_id``. Since this is an asynchronous operation by
|
||||
libtorrent, watch for the TorrentFileRenamedEvent to know when the
|
||||
files have been renamed.
|
||||
|
||||
@ -1258,7 +1256,7 @@ class Core(component.Component):
|
||||
@export
|
||||
def get_external_ip(self):
|
||||
"""
|
||||
Returns the external ip address recieved from libtorrent.
|
||||
Returns the external IP address received from libtorrent.
|
||||
"""
|
||||
return self.external_ip
|
||||
|
||||
|
@ -8,8 +8,6 @@
|
||||
#
|
||||
|
||||
"""The Deluge daemon"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
@ -200,6 +198,7 @@ class Daemon(object):
|
||||
if rpc not in self.get_method_list():
|
||||
return False
|
||||
|
||||
return self.rpcserver.get_session_auth_level() >= self.rpcserver.get_rpc_auth_level(
|
||||
rpc
|
||||
return (
|
||||
self.rpcserver.get_session_auth_level()
|
||||
>= self.rpcserver.get_rpc_auth_level(rpc)
|
||||
)
|
||||
|
@ -7,8 +7,6 @@
|
||||
# the additional special exception to link portions of this program with the OpenSSL library.
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
from logging import DEBUG, FileHandler, getLogger
|
||||
|
@ -7,8 +7,6 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
import deluge.component as component
|
||||
|
@ -7,12 +7,8 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from six import string_types
|
||||
|
||||
import deluge.component as component
|
||||
from deluge.common import TORRENT_STATE
|
||||
|
||||
@ -100,9 +96,7 @@ def tracker_error_filter(torrent_ids, values):
|
||||
|
||||
|
||||
class FilterManager(component.Component):
|
||||
"""FilterManager
|
||||
|
||||
"""
|
||||
"""FilterManager"""
|
||||
|
||||
def __init__(self, core):
|
||||
component.Component.__init__(self, 'FilterManager')
|
||||
@ -138,7 +132,7 @@ class FilterManager(component.Component):
|
||||
|
||||
# Sanitize input: filter-value must be a list of strings
|
||||
for key, value in filter_dict.items():
|
||||
if isinstance(value, string_types):
|
||||
if isinstance(value, str):
|
||||
filter_dict[key] = [value]
|
||||
|
||||
# Optimized filter for id
|
||||
|
@ -9,8 +9,6 @@
|
||||
|
||||
|
||||
"""PluginManager for Core"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from twisted.internet import defer
|
||||
|
@ -8,13 +8,13 @@
|
||||
#
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import threading
|
||||
from urllib.parse import quote_plus
|
||||
from urllib.request import urlopen
|
||||
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
@ -29,13 +29,6 @@ try:
|
||||
except ImportError:
|
||||
GeoIP = None
|
||||
|
||||
try:
|
||||
from urllib.parse import quote_plus
|
||||
from urllib.request import urlopen
|
||||
except ImportError:
|
||||
from urllib import quote_plus
|
||||
from urllib2 import urlopen
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_PREFS = {
|
||||
@ -231,7 +224,7 @@ class PreferencesManager(component.Component):
|
||||
self.core.apply_session_settings(
|
||||
{
|
||||
'listen_system_port_fallback': self.config['listen_use_sys_port'],
|
||||
'listen_interfaces': ''.join(interfaces),
|
||||
'listen_interfaces': ','.join(interfaces),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -8,17 +8,13 @@
|
||||
#
|
||||
|
||||
"""RPCServer Module"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import traceback
|
||||
from collections import namedtuple
|
||||
from types import FunctionType
|
||||
|
||||
from OpenSSL import crypto
|
||||
from twisted.internet import defer, reactor
|
||||
from twisted.internet.protocol import Factory, connectionDone
|
||||
|
||||
@ -29,7 +25,7 @@ from deluge.core.authmanager import (
|
||||
AUTH_LEVEL_DEFAULT,
|
||||
AUTH_LEVEL_NONE,
|
||||
)
|
||||
from deluge.crypto_utils import get_context_factory
|
||||
from deluge.crypto_utils import check_ssl_keys, get_context_factory
|
||||
from deluge.error import (
|
||||
DelugeError,
|
||||
IncompatibleClient,
|
||||
@ -588,59 +584,3 @@ class RPCServer(component.Component):
|
||||
|
||||
def stop(self):
|
||||
self.factory.state = 'stopping'
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
from deluge.common import PY2
|
||||
|
||||
digest = 'sha256' if not PY2 else b'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)
|
||||
|
@ -14,11 +14,10 @@ Attributes:
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import division, unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from twisted.internet.defer import Deferred, DeferredList
|
||||
|
||||
@ -34,18 +33,6 @@ from deluge.event import (
|
||||
TorrentTrackerStatusEvent,
|
||||
)
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
# PY2 fallback
|
||||
from urlparse import urlparse # pylint: disable=ungrouped-imports
|
||||
|
||||
try:
|
||||
from future_builtins import zip
|
||||
except ImportError:
|
||||
# Ignore on Py3.
|
||||
pass
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
LT_TORRENT_STATE_MAP = {
|
||||
@ -206,12 +193,12 @@ class Torrent(object):
|
||||
options (dict): The torrent options.
|
||||
state (TorrentState): The torrent state.
|
||||
filename (str): The filename of the torrent file.
|
||||
magnet (str): The magnet uri.
|
||||
magnet (str): The magnet URI.
|
||||
|
||||
Attributes:
|
||||
torrent_id (str): The torrent_id for this torrent
|
||||
handle: Holds the libtorrent torrent handle
|
||||
magnet (str): The magnet uri used to add this torrent (if available).
|
||||
magnet (str): The magnet URI used to add this torrent (if available).
|
||||
status: Holds status info so that we don"t need to keep getting it from libtorrent.
|
||||
torrent_info: store the torrent info.
|
||||
has_metadata (bool): True if the metadata for the torrent is available, False otherwise.
|
||||
@ -266,6 +253,9 @@ class Torrent(object):
|
||||
self.is_finished = False
|
||||
self.filename = filename
|
||||
|
||||
if not self.filename:
|
||||
self.filename = ''
|
||||
|
||||
self.forced_error = None
|
||||
self.statusmsg = None
|
||||
self.state = None
|
||||
@ -428,14 +418,14 @@ class Torrent(object):
|
||||
# Setting the priorites for all the pieces of this torrent
|
||||
self.handle.prioritize_pieces(priorities)
|
||||
|
||||
def set_sequential_download(self, set_sequencial):
|
||||
def set_sequential_download(self, sequential):
|
||||
"""Sets whether to download the pieces of the torrent in order.
|
||||
|
||||
Args:
|
||||
set_sequencial (bool): Enable sequencial downloading.
|
||||
sequential (bool): Enable sequential downloading.
|
||||
"""
|
||||
self.options['sequential_download'] = set_sequencial
|
||||
self.handle.set_sequential_download(set_sequencial)
|
||||
self.options['sequential_download'] = sequential
|
||||
self.handle.set_sequential_download(sequential)
|
||||
|
||||
def set_auto_managed(self, auto_managed):
|
||||
"""Set auto managed mode, i.e. will be started or queued automatically.
|
||||
@ -810,7 +800,11 @@ class Torrent(object):
|
||||
if peer.flags & peer.connecting or peer.flags & peer.handshake:
|
||||
continue
|
||||
|
||||
client = decode_bytes(peer.client)
|
||||
try:
|
||||
client = decode_bytes(peer.client)
|
||||
except UnicodeDecodeError:
|
||||
# libtorrent on Py3 can raise UnicodeDecodeError for peer_info.client
|
||||
client = 'unknown'
|
||||
|
||||
try:
|
||||
country = component.get('Core').geoip_instance.country_code_by_addr(
|
||||
@ -867,11 +861,18 @@ class Torrent(object):
|
||||
"""
|
||||
if not self.has_metadata:
|
||||
return []
|
||||
return [
|
||||
progress / _file.size if _file.size else 0.0
|
||||
for progress, _file in zip(
|
||||
|
||||
try:
|
||||
files_progresses = zip(
|
||||
self.handle.file_progress(), self.torrent_info.files()
|
||||
)
|
||||
except Exception:
|
||||
# Handle libtorrent >=2.0.0,<=2.0.4 file_progress error
|
||||
files_progresses = zip(iter(lambda: 0, 1), self.torrent_info.files())
|
||||
|
||||
return [
|
||||
progress / _file.size if _file.size else 0.0
|
||||
for progress, _file in files_progresses
|
||||
]
|
||||
|
||||
def get_tracker_host(self):
|
||||
@ -913,7 +914,7 @@ class Torrent(object):
|
||||
return ''
|
||||
|
||||
def get_magnet_uri(self):
|
||||
"""Returns a magnet uri for this torrent"""
|
||||
"""Returns a magnet URI for this torrent"""
|
||||
return lt.make_magnet_uri(self.handle)
|
||||
|
||||
def get_name(self):
|
||||
@ -987,6 +988,8 @@ class Torrent(object):
|
||||
call to get_status based on the session_id
|
||||
update (bool): If True the status will be updated from libtorrent
|
||||
if False, the cached values will be returned
|
||||
all_keys (bool): If True return all keys while ignoring the keys param
|
||||
if False, return only the requested keys
|
||||
|
||||
Returns:
|
||||
dict: a dictionary of the status keys and their values
|
||||
@ -1208,8 +1211,8 @@ class Torrent(object):
|
||||
bool: True is successful, otherwise False
|
||||
"""
|
||||
try:
|
||||
self.handle.connect_peer((peer_ip, peer_port), 0)
|
||||
except RuntimeError as ex:
|
||||
self.handle.connect_peer((peer_ip, int(peer_port)), 0)
|
||||
except (RuntimeError, ValueError) as ex:
|
||||
log.debug('Unable to connect to peer: %s', ex)
|
||||
return False
|
||||
return True
|
||||
@ -1312,7 +1315,7 @@ class Torrent(object):
|
||||
torrent_files = [
|
||||
os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')
|
||||
]
|
||||
if delete_copies:
|
||||
if delete_copies and self.filename:
|
||||
torrent_files.append(
|
||||
os.path.join(self.config['torrentfiles_location'], self.filename)
|
||||
)
|
||||
@ -1336,8 +1339,8 @@ class Torrent(object):
|
||||
def scrape_tracker(self):
|
||||
"""Scrape the tracker
|
||||
|
||||
A scrape request queries the tracker for statistics such as total
|
||||
number of incomplete peers, complete peers, number of downloads etc.
|
||||
A scrape request queries the tracker for statistics such as total
|
||||
number of incomplete peers, complete peers, number of downloads etc.
|
||||
"""
|
||||
try:
|
||||
self.handle.scrape_tracker()
|
||||
@ -1384,7 +1387,7 @@ class Torrent(object):
|
||||
This basically does a file rename on all of the folders children.
|
||||
|
||||
Args:
|
||||
folder (str): The orignal folder name
|
||||
folder (str): The original folder name
|
||||
new_folder (str): The new folder name
|
||||
|
||||
Returns:
|
||||
|
@ -8,24 +8,28 @@
|
||||
#
|
||||
|
||||
"""TorrentManager handles Torrent objects"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import operator
|
||||
import os
|
||||
import pickle
|
||||
import time
|
||||
from collections import namedtuple
|
||||
from tempfile import gettempdir
|
||||
|
||||
import six.moves.cPickle as pickle # noqa: N813
|
||||
from twisted.internet import defer, error, reactor, threads
|
||||
from twisted.internet.defer import Deferred, DeferredList
|
||||
from twisted.internet.task import LoopingCall
|
||||
|
||||
import deluge.component as component
|
||||
from deluge._libtorrent import lt
|
||||
from deluge.common import archive_files, decode_bytes, get_magnet_info, is_magnet
|
||||
from deluge._libtorrent import LT_VERSION, lt
|
||||
from deluge.common import (
|
||||
VersionSplit,
|
||||
archive_files,
|
||||
decode_bytes,
|
||||
get_magnet_info,
|
||||
is_magnet,
|
||||
)
|
||||
from deluge.configmanager import ConfigManager, get_config_dir
|
||||
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
|
||||
from deluge.core.torrent import Torrent, TorrentOptions, sanitize_filepath
|
||||
@ -89,7 +93,7 @@ class TorrentState: # pylint: disable=old-style-class
|
||||
super_seeding=False,
|
||||
name=None,
|
||||
):
|
||||
# Build the class atrribute list from args
|
||||
# Build the class attribute list from args
|
||||
for key, value in locals().items():
|
||||
if key == 'self':
|
||||
continue
|
||||
@ -340,11 +344,11 @@ class TorrentManager(component.Component):
|
||||
return torrent_info
|
||||
|
||||
def prefetch_metadata(self, magnet, timeout):
|
||||
"""Download the metadata for a magnet uri.
|
||||
"""Download the metadata for a magnet URI.
|
||||
|
||||
Args:
|
||||
magnet (str): A magnet uri to download the metadata for.
|
||||
timeout (int): Number of seconds to wait before cancelling.
|
||||
magnet (str): A magnet URI to download the metadata for.
|
||||
timeout (int): Number of seconds to wait before canceling.
|
||||
|
||||
Returns:
|
||||
Deferred: A tuple of (torrent_id (str), metadata (dict))
|
||||
@ -434,6 +438,11 @@ class TorrentManager(component.Component):
|
||||
add_torrent_params['url'] = magnet.strip().encode('utf8')
|
||||
add_torrent_params['name'] = magnet_info['name']
|
||||
torrent_id = magnet_info['info_hash']
|
||||
# Workaround lt 1.2 bug for magnet resume data with no metadata
|
||||
if resume_data and VersionSplit(LT_VERSION) >= VersionSplit('1.2.10.0'):
|
||||
add_torrent_params['info_hash'] = bytes(
|
||||
bytearray.fromhex(torrent_id)
|
||||
)
|
||||
else:
|
||||
raise AddTorrentError(
|
||||
'Unable to add magnet, invalid magnet info: %s' % magnet
|
||||
@ -509,7 +518,7 @@ class TorrentManager(component.Component):
|
||||
save_state (bool, optional): If True save the session state after adding torrent, defaults to True.
|
||||
filedump (str, optional): bencoded filedump of a torrent file.
|
||||
filename (str, optional): The filename of the torrent file.
|
||||
magnet (str, optional): The magnet uri.
|
||||
magnet (str, optional): The magnet URI.
|
||||
resume_data (lt.entry, optional): libtorrent fast resume data.
|
||||
|
||||
Returns:
|
||||
@ -574,7 +583,7 @@ class TorrentManager(component.Component):
|
||||
save_state (bool, optional): If True save the session state after adding torrent, defaults to True.
|
||||
filedump (str, optional): bencoded filedump of a torrent file.
|
||||
filename (str, optional): The filename of the torrent file.
|
||||
magnet (str, optional): The magnet uri.
|
||||
magnet (str, optional): The magnet URI.
|
||||
resume_data (lt.entry, optional): libtorrent fast resume data.
|
||||
|
||||
Returns:
|
||||
@ -642,7 +651,7 @@ class TorrentManager(component.Component):
|
||||
# Resume AlertManager if paused for adding torrent to libtorrent.
|
||||
component.resume('AlertManager')
|
||||
|
||||
# Store the orignal resume_data, in case of errors.
|
||||
# Store the original resume_data, in case of errors.
|
||||
if resume_data:
|
||||
self.resume_data[torrent.torrent_id] = resume_data
|
||||
|
||||
@ -809,7 +818,7 @@ class TorrentManager(component.Component):
|
||||
|
||||
try:
|
||||
with open(filepath, 'rb') as _file:
|
||||
state = pickle.load(_file)
|
||||
state = pickle.load(_file, encoding='utf8')
|
||||
except (IOError, EOFError, pickle.UnpicklingError) as ex:
|
||||
message = 'Unable to load {}: {}'.format(filepath, ex)
|
||||
log.error(message)
|
||||
@ -1022,7 +1031,7 @@ class TorrentManager(component.Component):
|
||||
)
|
||||
|
||||
def on_torrent_resume_save(dummy_result, torrent_id):
|
||||
"""Recieved torrent resume_data alert so remove from waiting list"""
|
||||
"""Received torrent resume_data alert so remove from waiting list"""
|
||||
self.waiting_on_resume_data.pop(torrent_id, None)
|
||||
|
||||
deferreds = []
|
||||
@ -1240,7 +1249,7 @@ class TorrentManager(component.Component):
|
||||
def on_alert_add_torrent(self, alert):
|
||||
"""Alert handler for libtorrent add_torrent_alert"""
|
||||
if not alert.handle.is_valid():
|
||||
log.warning('Torrent handle is invalid!')
|
||||
log.warning('Torrent handle is invalid: %s', alert.error.message())
|
||||
return
|
||||
|
||||
try:
|
||||
@ -1389,7 +1398,22 @@ class TorrentManager(component.Component):
|
||||
log.debug(
|
||||
'Tracker Error Alert: %s [%s]', decode_bytes(alert.message()), error_message
|
||||
)
|
||||
torrent.set_tracker_status('Error: ' + error_message)
|
||||
if VersionSplit(LT_VERSION) >= VersionSplit('1.2.0.0'):
|
||||
# libtorrent 1.2 added endpoint struct to each tracker. to prevent false updates
|
||||
# we will need to verify that at least one endpoint to the errored tracker is working
|
||||
for tracker in torrent.handle.trackers():
|
||||
if tracker['url'] == alert.url:
|
||||
if any(
|
||||
endpoint['last_error']['value'] == 0
|
||||
for endpoint in tracker['endpoints']
|
||||
):
|
||||
torrent.set_tracker_status('Announce OK')
|
||||
else:
|
||||
torrent.set_tracker_status('Error: ' + error_message)
|
||||
break
|
||||
else:
|
||||
# preserve old functionality for libtorrent < 1.2
|
||||
torrent.set_tracker_status('Error: ' + error_message)
|
||||
|
||||
def on_alert_storage_moved(self, alert):
|
||||
"""Alert handler for libtorrent storage_moved_alert"""
|
||||
@ -1596,18 +1620,9 @@ class TorrentManager(component.Component):
|
||||
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
|
||||
|
||||
def on_alert_external_ip(self, alert):
|
||||
"""Alert handler for libtorrent external_ip_alert
|
||||
|
||||
Note:
|
||||
The alert.message IPv4 address format is:
|
||||
'external IP received: 0.0.0.0'
|
||||
and IPv6 address format is:
|
||||
'external IP received: 0:0:0:0:0:0:0:0'
|
||||
"""
|
||||
|
||||
external_ip = decode_bytes(alert.message()).split(' ')[-1]
|
||||
log.info('on_alert_external_ip: %s', external_ip)
|
||||
component.get('EventManager').emit(ExternalIPEvent(external_ip))
|
||||
"""Alert handler for libtorrent external_ip_alert"""
|
||||
log.info('on_alert_external_ip: %s', alert.external_address)
|
||||
component.get('EventManager').emit(ExternalIPEvent(alert.external_address))
|
||||
|
||||
def on_alert_performance(self, alert):
|
||||
"""Alert handler for libtorrent performance_alert"""
|
||||
|
@ -7,8 +7,10 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import os
|
||||
import stat
|
||||
|
||||
from OpenSSL import crypto
|
||||
from OpenSSL.crypto import FILETYPE_PEM
|
||||
from twisted.internet.ssl import (
|
||||
AcceptableCiphers,
|
||||
@ -18,6 +20,8 @@ from twisted.internet.ssl import (
|
||||
TLSVersion,
|
||||
)
|
||||
|
||||
import deluge.configmanager
|
||||
|
||||
# A TLS ciphers list.
|
||||
# Sources for more information on TLS ciphers:
|
||||
# - https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
@ -77,3 +81,57 @@ def get_context_factory(cert_path, pkey_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)
|
||||
|
@ -7,8 +7,6 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import inspect
|
||||
import re
|
||||
import warnings
|
||||
@ -56,7 +54,7 @@ def overrides(*args):
|
||||
if inspect.isfunction(args[0]):
|
||||
return _overrides(stack, args[0])
|
||||
else:
|
||||
# One or more classes are specifed, so return a function that will be
|
||||
# One or more classes are specified, so return a function that will be
|
||||
# called with the real function as argument
|
||||
def ret_func(func, **kwargs):
|
||||
return _overrides(stack, func, explicit_base_classes=args)
|
||||
@ -107,7 +105,7 @@ def _overrides(stack, method, explicit_base_classes=None):
|
||||
for c in base_classes + check_classes:
|
||||
classes[c] = get_class(c)
|
||||
|
||||
# Verify that the excplicit override class is one of base classes
|
||||
# Verify that the explicit override class is one of base classes
|
||||
if explicit_base_classes:
|
||||
from itertools import product
|
||||
|
||||
@ -146,7 +144,7 @@ def _overrides(stack, method, explicit_base_classes=None):
|
||||
def deprecated(func):
|
||||
"""This is a decorator which can be used to mark function as deprecated.
|
||||
|
||||
It will result in a warning being emmitted when the function is used.
|
||||
It will result in a warning being emitted when the function is used.
|
||||
|
||||
"""
|
||||
|
||||
|
@ -9,9 +9,6 @@
|
||||
#
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class DelugeError(Exception):
|
||||
def __new__(cls, *args, **kwargs):
|
||||
inst = super(DelugeError, cls).__new__(cls, *args, **kwargs)
|
||||
@ -94,3 +91,7 @@ class AuthenticationRequired(_UsernameBasedPasstroughError):
|
||||
|
||||
class AuthManagerError(_UsernameBasedPasstroughError):
|
||||
pass
|
||||
|
||||
|
||||
class LibtorrentImportError(ImportError):
|
||||
pass
|
||||
|
@ -14,10 +14,6 @@ This module describes the types of events that can be generated by the daemon
|
||||
and subsequently emitted to the clients.
|
||||
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import six
|
||||
|
||||
known_events = {}
|
||||
|
||||
|
||||
@ -32,7 +28,7 @@ class DelugeEventMetaClass(type):
|
||||
known_events[name] = cls
|
||||
|
||||
|
||||
class DelugeEvent(six.with_metaclass(DelugeEventMetaClass, object)):
|
||||
class DelugeEvent(metaclass=DelugeEventMetaClass):
|
||||
"""
|
||||
The base class for all events.
|
||||
|
||||
|
@ -7,8 +7,6 @@
|
||||
# See LICENSE for more details.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import cgi
|
||||
import logging
|
||||
import os.path
|
||||
@ -151,9 +149,12 @@ class HTTPDownloaderAgent(object):
|
||||
|
||||
self.filename = new_file_name
|
||||
|
||||
cont_type = headers.getRawHeaders(b'content-type')[0].decode()
|
||||
params = cgi.parse_header(cont_type)[1]
|
||||
encoding = params.get('charset', None)
|
||||
cont_type_header = headers.getRawHeaders(b'content-type')[0].decode()
|
||||
cont_type, params = cgi.parse_header(cont_type_header)
|
||||
# Only re-ecode text content types.
|
||||
encoding = None
|
||||
if cont_type.startswith('text/'):
|
||||
encoding = params.get('charset', None)
|
||||
response.deliverBody(
|
||||
BodyHandler(response.request, finished, body_length, self, encoding)
|
||||
)
|
||||
|
6178
deluge/i18n/af.po
Normal file
6178
deluge/i18n/af.po
Normal file
File diff suppressed because it is too large
Load Diff
1432
deluge/i18n/ar.po
1432
deluge/i18n/ar.po
File diff suppressed because it is too large
Load Diff
8048
deluge/i18n/ast.po
8048
deluge/i18n/ast.po
File diff suppressed because it is too large
Load Diff
7272
deluge/i18n/be.po
7272
deluge/i18n/be.po
File diff suppressed because it is too large
Load Diff
8134
deluge/i18n/bg.po
8134
deluge/i18n/bg.po
File diff suppressed because it is too large
Load Diff
6763
deluge/i18n/bn.po
6763
deluge/i18n/bn.po
File diff suppressed because it is too large
Load Diff
6820
deluge/i18n/bs.po
6820
deluge/i18n/bs.po
File diff suppressed because it is too large
Load Diff
2281
deluge/i18n/ca.po
2281
deluge/i18n/ca.po
File diff suppressed because it is too large
Load Diff
1615
deluge/i18n/cs.po
1615
deluge/i18n/cs.po
File diff suppressed because it is too large
Load Diff
6812
deluge/i18n/cy.po
6812
deluge/i18n/cy.po
File diff suppressed because it is too large
Load Diff
@ -7,53 +7,53 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: deluge\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2019-06-06 11:53+0100\n"
|
||||
"PO-Revision-Date: 2019-01-17 20:26+0000\n"
|
||||
"POT-Creation-Date: 2019-11-12 14:55+0000\n"
|
||||
"PO-Revision-Date: 2019-07-23 10:47+0000\n"
|
||||
"Last-Translator: scootergrisen <scootergrisen@gmail.com>\n"
|
||||
"Language-Team: Danish <da@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2019-06-06 11:12+0000\n"
|
||||
"X-Generator: Launchpad (build 18978)\n"
|
||||
"X-Launchpad-Export-Date: 2021-09-10 18:01+0000\n"
|
||||
"X-Generator: Launchpad (build aca2013fd8cd2fea408d75f89f9bc012fbab307d)\n"
|
||||
|
||||
#: deluge/common.py:405
|
||||
#: deluge/common.py:411
|
||||
msgid "B"
|
||||
msgstr "B"
|
||||
|
||||
#: deluge/common.py:406
|
||||
#: deluge/common.py:412
|
||||
msgid "KiB"
|
||||
msgstr "KiB"
|
||||
|
||||
#: deluge/common.py:407
|
||||
#: deluge/common.py:413
|
||||
msgid "MiB"
|
||||
msgstr "MiB"
|
||||
|
||||
#: deluge/common.py:408
|
||||
#: deluge/common.py:414
|
||||
msgid "GiB"
|
||||
msgstr "GiB"
|
||||
|
||||
#: deluge/common.py:409
|
||||
#: deluge/common.py:415
|
||||
msgid "TiB"
|
||||
msgstr "TiB"
|
||||
|
||||
#: deluge/common.py:410
|
||||
#: deluge/common.py:416
|
||||
msgid "K"
|
||||
msgstr "K"
|
||||
|
||||
#: deluge/common.py:411
|
||||
#: deluge/common.py:417
|
||||
msgid "M"
|
||||
msgstr "M"
|
||||
|
||||
#: deluge/common.py:412
|
||||
#: deluge/common.py:418
|
||||
msgid "G"
|
||||
msgstr "G"
|
||||
|
||||
#: deluge/common.py:413
|
||||
#: deluge/common.py:419
|
||||
msgid "T"
|
||||
msgstr "T"
|
||||
|
||||
#: deluge/common.py:509 deluge/ui/gtk3/statusbar.py:442
|
||||
#: deluge/common.py:515 deluge/ui/gtk3/statusbar.py:442
|
||||
#: deluge/ui/gtk3/statusbar.py:455 deluge/ui/gtk3/statusbar.py:464
|
||||
#: deluge/ui/gtk3/statusbar.py:477 deluge/ui/gtk3/statusbar.py:484
|
||||
#: deluge/ui/gtk3/statusbar.py:526 deluge/ui/gtk3/statusbar.py:542
|
||||
@ -64,7 +64,7 @@ msgstr "T"
|
||||
msgid "K/s"
|
||||
msgstr "K/s"
|
||||
|
||||
#: deluge/common.py:509 deluge/ui/gtk3/menubar.py:449
|
||||
#: deluge/common.py:515 deluge/ui/gtk3/menubar.py:449
|
||||
#: deluge/ui/gtk3/menubar.py:455
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:80
|
||||
#: deluge/ui/console/widgets/statusbars.py:104
|
||||
@ -78,27 +78,27 @@ msgstr "K/s"
|
||||
msgid "KiB/s"
|
||||
msgstr "KiB/s"
|
||||
|
||||
#: deluge/common.py:515
|
||||
#: deluge/common.py:521
|
||||
msgid "M/s"
|
||||
msgstr "M/s"
|
||||
|
||||
#: deluge/common.py:515
|
||||
#: deluge/common.py:521
|
||||
msgid "MiB/s"
|
||||
msgstr "MiB/s"
|
||||
|
||||
#: deluge/common.py:521
|
||||
#: deluge/common.py:527
|
||||
msgid "G/s"
|
||||
msgstr "G/s"
|
||||
|
||||
#: deluge/common.py:521
|
||||
#: deluge/common.py:527
|
||||
msgid "GiB/s"
|
||||
msgstr "GiB/s"
|
||||
|
||||
#: deluge/common.py:527
|
||||
#: deluge/common.py:533
|
||||
msgid "T/s"
|
||||
msgstr "T/s"
|
||||
|
||||
#: deluge/common.py:527
|
||||
#: deluge/common.py:533
|
||||
msgid "TiB/s"
|
||||
msgstr "TiB/s"
|
||||
|
||||
@ -191,7 +191,7 @@ msgstr ""
|
||||
msgid "Config keys to be unmodified by `set_config` RPC"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/common.py:37 deluge/ui/gtk3/filtertreeview.py:135
|
||||
#: deluge/ui/common.py:37 deluge/ui/gtk3/filtertreeview.py:130
|
||||
#: deluge/ui/web/js/deluge-all/UI.js:18
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
@ -232,7 +232,7 @@ msgid "Queued"
|
||||
msgstr "Sat i kø"
|
||||
|
||||
#: deluge/ui/common.py:45 deluge/ui/common.py:122
|
||||
#: deluge/ui/gtk3/statusbar.py:396 deluge/ui/gtk3/filtertreeview.py:136
|
||||
#: deluge/ui/gtk3/statusbar.py:396 deluge/ui/gtk3/filtertreeview.py:131
|
||||
#: deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py:330
|
||||
#: deluge/ui/web/js/deluge-all/AddConnectionWindow.js:94
|
||||
#: deluge/ui/web/js/deluge-all/EditConnectionWindow.js:114
|
||||
@ -241,7 +241,9 @@ msgstr "Sat i kø"
|
||||
#: deluge/ui/web/js/deluge-all/ConnectionManager.js:417
|
||||
#: deluge/ui/web/js/deluge-all/UI.js:27
|
||||
#: deluge/ui/web/js/deluge-all/details/StatusTab.js:121
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:301
|
||||
#: deluge/ui/web/js/deluge-all/add/UrlWindow.js:98
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:291
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:316
|
||||
msgid "Error"
|
||||
msgstr "Fejl"
|
||||
|
||||
@ -265,7 +267,7 @@ msgid "State"
|
||||
msgstr "Status"
|
||||
|
||||
#: deluge/ui/common.py:54 deluge/ui/gtk3/createtorrentdialog.py:72
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:118 deluge/ui/gtk3/files_tab.py:113
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:123 deluge/ui/gtk3/files_tab.py:113
|
||||
#: deluge/ui/gtk3/torrentview.py:283
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:174
|
||||
#: deluge/ui/console/modes/preferences/preference_panes.py:738
|
||||
@ -449,7 +451,7 @@ msgstr "Sti til flyt fuldførte"
|
||||
msgid "Move On Completed Path"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/common.py:115 deluge/ui/gtk3/filtertreeview.py:140
|
||||
#: deluge/ui/common.py:115 deluge/ui/gtk3/filtertreeview.py:135
|
||||
#: deluge/ui/gtk3/torrentview.py:416
|
||||
#: deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py:499
|
||||
#: deluge/ui/web/js/deluge-all/FilterPanel.js:32
|
||||
@ -1621,7 +1623,7 @@ msgid "Daemon not running"
|
||||
msgstr "Dæmon kører ikke"
|
||||
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:62
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:105 deluge/ui/gtk3/files_tab.py:92
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:110 deluge/ui/gtk3/files_tab.py:92
|
||||
#: deluge/ui/web/js/deluge-all/details/FilesTab.js:18
|
||||
#: deluge/ui/web/js/deluge-all/add/FilesTab.js:28
|
||||
msgid "Filename"
|
||||
@ -1640,7 +1642,7 @@ msgstr "Vælg en fil"
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:132
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:169
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:258
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:690 deluge/ui/gtk3/dialogs.py:203
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:698 deluge/ui/gtk3/dialogs.py:203
|
||||
#: deluge/ui/gtk3/dialogs.py:261 deluge/ui/gtk3/dialogs.py:273
|
||||
#: deluge/ui/gtk3/dialogs.py:364 deluge/ui/gtk3/dialogs.py:427
|
||||
#: deluge/ui/gtk3/preferences.py:1158
|
||||
@ -1664,7 +1666,7 @@ msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:134
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:171
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:692 deluge/ui/gtk3/preferences.py:1160
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:700 deluge/ui/gtk3/preferences.py:1160
|
||||
msgid "_Open"
|
||||
msgstr ""
|
||||
|
||||
@ -1685,29 +1687,29 @@ msgid "_Save"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:271
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:704
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:712
|
||||
msgid "Torrent files"
|
||||
msgstr "Torrent-filer"
|
||||
|
||||
#: deluge/ui/gtk3/createtorrentdialog.py:275
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:708
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:716
|
||||
msgid "All files"
|
||||
msgstr "Alle filer"
|
||||
|
||||
#: deluge/ui/gtk3/mainwindow.py:185
|
||||
#: deluge/ui/gtk3/mainwindow.py:192
|
||||
msgid "Enter your password to show Deluge..."
|
||||
msgstr "Indtast din adgangskode for at vise Deluge..."
|
||||
|
||||
#: deluge/ui/gtk3/mainwindow.py:244
|
||||
#: deluge/ui/gtk3/mainwindow.py:251
|
||||
msgid "Enter your password to Quit Deluge..."
|
||||
msgstr "Indtast din adgangskode for at afslutte Deluge..."
|
||||
|
||||
#: deluge/ui/gtk3/mainwindow.py:336
|
||||
#: deluge/ui/gtk3/mainwindow.py:343
|
||||
#, python-brace-format
|
||||
msgid "D: {download_rate} U: {upload_rate} - Deluge"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/mainwindow.py:350 deluge/ui/gtk3/aboutdialog.py:26
|
||||
#: deluge/ui/gtk3/mainwindow.py:357 deluge/ui/gtk3/aboutdialog.py:26
|
||||
#: deluge/ui/gtk3/aboutdialog.py:27 deluge/ui/gtk3/systemtray.py:96
|
||||
#: deluge/ui/gtk3/systemtray.py:184 deluge/ui/gtk3/systemtray.py:244
|
||||
#: deluge/ui/data/share/applications/deluge.desktop.in.h:1
|
||||
@ -1716,6 +1718,16 @@ msgstr ""
|
||||
msgid "Deluge"
|
||||
msgstr "Deluge"
|
||||
|
||||
#: deluge/ui/gtk3/path_combo_chooser.py:393
|
||||
#: deluge/ui/gtk3/glade/path_combo_chooser.ui.h:20
|
||||
msgid "Edit path"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/path_combo_chooser.py:395
|
||||
#: deluge/ui/gtk3/glade/path_combo_chooser.ui.h:21
|
||||
msgid "Remove path"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/options_tab.py:136
|
||||
msgid "_Apply to selected"
|
||||
msgstr ""
|
||||
@ -1806,59 +1818,59 @@ msgstr "Server:"
|
||||
msgid "libtorrent:"
|
||||
msgstr "libtorrent:"
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:97 deluge/ui/gtk3/queuedtorrents.py:51
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:102 deluge/ui/gtk3/queuedtorrents.py:51
|
||||
msgid "Torrent"
|
||||
msgstr "Torrent"
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:224
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:232
|
||||
#, python-format
|
||||
msgid "Add Torrents (%d)"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:230
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:238
|
||||
msgid "Duplicate torrent(s)"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:232
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:240
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You cannot add the same torrent twice. %d torrents were already added."
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:247
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:255
|
||||
msgid "Invalid File"
|
||||
msgstr "Ugyldig fil"
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:282
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:290
|
||||
#: deluge/ui/gtk3/glade/add_torrent_dialog.ui.h:8
|
||||
msgid "Please wait for files..."
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:288
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:296
|
||||
msgid "Unable to download files for this magnet"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:686
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:694
|
||||
msgid "Choose a .torrent file"
|
||||
msgstr "Vælg en .torrent-fil"
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:769
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:777
|
||||
msgid "Invalid URL"
|
||||
msgstr "Ugyldig URL"
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:770
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:778
|
||||
msgid "is not a valid URL."
|
||||
msgstr "er ikke et gyldigt URL."
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:776
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:784
|
||||
msgid "Downloading..."
|
||||
msgstr "Downloader..."
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:811
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:819
|
||||
msgid "Download Failed"
|
||||
msgstr "Download mislykkedes"
|
||||
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:812
|
||||
#: deluge/ui/gtk3/addtorrentdialog.py:820
|
||||
msgid "Failed to download:"
|
||||
msgstr "Fejlslagne download(s):"
|
||||
|
||||
@ -2153,29 +2165,29 @@ msgstr "Ned"
|
||||
msgid "Up"
|
||||
msgstr "Op"
|
||||
|
||||
#: deluge/ui/gtk3/gtkui.py:318
|
||||
#: deluge/ui/gtk3/gtkui.py:313
|
||||
msgid ""
|
||||
"A Deluge daemon (deluged) is already running.\n"
|
||||
"To use Standalone mode, stop local daemon and restart Deluge."
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/gtkui.py:324
|
||||
#: deluge/ui/gtk3/gtkui.py:319
|
||||
msgid ""
|
||||
"Only Thin Client mode is available because libtorrent is not installed.\n"
|
||||
"To use Standalone mode, please install libtorrent package."
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/gtkui.py:330 deluge/ui/gtk3/gtkui.py:336
|
||||
#: deluge/ui/gtk3/gtkui.py:325 deluge/ui/gtk3/gtkui.py:331
|
||||
msgid ""
|
||||
"Only Thin Client mode is available due to unknown Import Error.\n"
|
||||
"To use Standalone mode, please see logs for error details."
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/gtkui.py:354
|
||||
#: deluge/ui/gtk3/gtkui.py:349
|
||||
msgid "Continue in Thin Client mode?"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/gtkui.py:355
|
||||
#: deluge/ui/gtk3/gtkui.py:350
|
||||
msgid "Change User Interface Mode"
|
||||
msgstr ""
|
||||
|
||||
@ -2214,7 +2226,7 @@ msgstr "Version"
|
||||
#: deluge/ui/gtk3/connectionmanager.py:219
|
||||
#: deluge/ui/gtk3/glade/connection_manager.ui.h:8
|
||||
msgid "_Start Daemon"
|
||||
msgstr ""
|
||||
msgstr "_Start dæmon"
|
||||
|
||||
#: deluge/ui/gtk3/connectionmanager.py:250
|
||||
msgid "_Stop Daemon"
|
||||
@ -2297,6 +2309,15 @@ msgstr ""
|
||||
msgid "You must now restart the deluge UI for the changes to take effect."
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/preferences.py:940
|
||||
msgid "Thinclient"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/preferences.py:940
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:18
|
||||
msgid "Standalone"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/preferences.py:942
|
||||
msgid "Switching Deluge Client Mode..."
|
||||
msgstr ""
|
||||
@ -2365,39 +2386,39 @@ msgstr ""
|
||||
msgid "An error occurred while removing account"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/filtertreeview.py:127
|
||||
#: deluge/ui/gtk3/filtertreeview.py:122
|
||||
#: deluge/ui/web/js/deluge-all/FilterPanel.js:28
|
||||
msgid "States"
|
||||
msgstr "Tilstande"
|
||||
|
||||
#: deluge/ui/gtk3/filtertreeview.py:133
|
||||
#: deluge/ui/gtk3/filtertreeview.py:128
|
||||
#: deluge/ui/gtk3/glade/create_torrent_dialog.ui.h:23
|
||||
#: deluge/plugins/Label/deluge_label/data/label_options.ui.h:21
|
||||
#: deluge/ui/web/js/deluge-all/FilterPanel.js:30
|
||||
msgid "Trackers"
|
||||
msgstr "Trackere"
|
||||
|
||||
#: deluge/ui/gtk3/filtertreeview.py:137 deluge/ui/gtk3/filtertreeview.py:143
|
||||
#: deluge/ui/gtk3/filtertreeview.py:132 deluge/ui/gtk3/filtertreeview.py:138
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:7
|
||||
#: deluge/ui/web/js/deluge-all/preferences/ProxyField.js:33
|
||||
msgid "None"
|
||||
msgstr "Ingen"
|
||||
|
||||
#: deluge/ui/gtk3/filtertreeview.py:142
|
||||
#: deluge/ui/gtk3/filtertreeview.py:137
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: deluge/ui/gtk3/filtertreeview.py:164
|
||||
#: deluge/ui/gtk3/filtertreeview.py:159
|
||||
#: deluge/ui/web/js/deluge-all/FilterPanel.js:34
|
||||
msgid "Labels"
|
||||
msgstr "Etiketter"
|
||||
|
||||
#: deluge/ui/gtk3/filtertreeview.py:209
|
||||
#: deluge/ui/gtk3/filtertreeview.py:204
|
||||
#: deluge/plugins/Label/deluge_label/gtkui/submenu.py:28
|
||||
msgid "No Label"
|
||||
msgstr "Ingen Etiket"
|
||||
|
||||
#: deluge/ui/gtk3/filtertreeview.py:211
|
||||
#: deluge/ui/gtk3/filtertreeview.py:206
|
||||
msgid "No Owner"
|
||||
msgstr ""
|
||||
|
||||
@ -2536,7 +2557,7 @@ msgstr "Afslut og _stop dæmon"
|
||||
#: deluge/ui/gtk3/glade/main_window.ui.h:5
|
||||
#: deluge/ui/gtk3/glade/tray_menu.ui.h:8
|
||||
msgid "_Quit"
|
||||
msgstr ""
|
||||
msgstr "_Afslut"
|
||||
|
||||
#: deluge/ui/gtk3/glade/main_window.ui.h:6
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:192
|
||||
@ -2794,10 +2815,6 @@ msgstr ""
|
||||
msgid "I2P"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:18
|
||||
msgid "Standalone"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:19
|
||||
msgid "The standalone self-contained application"
|
||||
msgstr ""
|
||||
@ -2912,7 +2929,7 @@ msgid "System Default"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:48
|
||||
msgid "<b>Languge</b>"
|
||||
msgid "<b>Language</b>"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/glade/preferences_dialog.ui.h:49
|
||||
@ -3740,14 +3757,6 @@ msgstr ""
|
||||
msgid "Ctrl+D"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/glade/path_combo_chooser.ui.h:20
|
||||
msgid "Edit path"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/glade/path_combo_chooser.ui.h:21
|
||||
msgid "Remove path"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/gtk3/glade/path_combo_chooser.ui.h:22
|
||||
msgid "Toggle hidden files"
|
||||
msgstr ""
|
||||
@ -5528,36 +5537,36 @@ msgstr "Pop op-notifikation er ikke slået til."
|
||||
msgid "libnotify is not installed"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:183
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:185
|
||||
msgid "Failed to popup notification"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:186
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:188
|
||||
msgid "Notification popup shown"
|
||||
msgstr "Notifikations-pop op vist"
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:190
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:192
|
||||
msgid "Sound notification not enabled"
|
||||
msgstr "Lydnotifikation ikke slået til"
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:192
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:194
|
||||
msgid "pygame is not installed"
|
||||
msgstr "pygame er ikke installeret"
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:204
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:206
|
||||
#, python-format
|
||||
msgid "Sound notification failed %s"
|
||||
msgstr "Lydnotifikation fejlede %s"
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:208
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:210
|
||||
msgid "Sound notification Success"
|
||||
msgstr "Lydpåmindelse lykkedes"
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:232
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:234
|
||||
msgid "Finished Torrent"
|
||||
msgstr "Afsluttet torrent"
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:236
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:238
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The torrent \"%(name)s\" including %(num_files)i file(s) has finished "
|
||||
@ -5566,12 +5575,12 @@ msgstr ""
|
||||
"Torrenten \"%(name)s\" inklusiv %(num_files)i fil(er) er færdige med at "
|
||||
"downloade."
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:285
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:315
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:287
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:317
|
||||
msgid "Notifications"
|
||||
msgstr "Notifikationer"
|
||||
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:661
|
||||
#: deluge/plugins/Notifications/deluge_notifications/gtkui.py:663
|
||||
msgid "Choose Sound File"
|
||||
msgstr "Vælg lydfil"
|
||||
|
||||
@ -6173,6 +6182,10 @@ msgstr "Adresse"
|
||||
msgid "Cookies"
|
||||
msgstr "Cookies"
|
||||
|
||||
#: deluge/ui/web/js/deluge-all/add/UrlWindow.js:99
|
||||
msgid "Failed to download torrent"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:133
|
||||
msgid "File"
|
||||
msgstr "Fil"
|
||||
@ -6181,11 +6194,15 @@ msgstr "Fil"
|
||||
msgid "Infohash"
|
||||
msgstr "Infohash"
|
||||
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:259
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:260
|
||||
msgid "Uploading your torrent..."
|
||||
msgstr "Uploader din torrent..."
|
||||
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:302
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:292
|
||||
msgid "Failed to upload torrent"
|
||||
msgstr ""
|
||||
|
||||
#: deluge/ui/web/js/deluge-all/add/AddWindow.js:317
|
||||
msgid "Not a valid torrent"
|
||||
msgstr "Ikke en gyldig torrent"
|
||||
|
||||
@ -6220,171 +6237,3 @@ msgstr ""
|
||||
#: deluge/ui/web/render/tab_status.html:26
|
||||
msgid "Date Added:"
|
||||
msgstr "Dato tilføjet:"
|
||||
|
||||
#~ msgid "pynotify is not installed"
|
||||
#~ msgstr "pynotify er ikke installeret"
|
||||
|
||||
#~ msgid "pynotify failed to show notification"
|
||||
#~ msgstr "pynotify kunne ikke vise notifikation"
|
||||
|
||||
#~ msgid "<b><i><big>Notifications</big></i></b>"
|
||||
#~ msgstr "<b><i><big>Notifikationer</big></i></b>"
|
||||
|
||||
#~ msgid "_Normal Priority"
|
||||
#~ msgstr "_Normal prioritet"
|
||||
|
||||
#~ msgid "_High Priority"
|
||||
#~ msgstr "_Høj prioritet"
|
||||
|
||||
#~ msgid "Associate Magnet links with Deluge"
|
||||
#~ msgstr "Associer Magnet-links med Deluge"
|
||||
|
||||
#~ msgid "Bulgarian"
|
||||
#~ msgstr "Bulgarsk"
|
||||
|
||||
#~ msgid "Arabic"
|
||||
#~ msgstr "Arabisk"
|
||||
|
||||
#~ msgid "German"
|
||||
#~ msgstr "Tysk"
|
||||
|
||||
#~ msgid "Danish"
|
||||
#~ msgstr "Dansk"
|
||||
|
||||
#~ msgid "Bosnian"
|
||||
#~ msgstr "Bosnisk"
|
||||
|
||||
#~ msgid "Czech"
|
||||
#~ msgstr "Tjekkisk"
|
||||
|
||||
#~ msgid "Belarusian"
|
||||
#~ msgstr "Hviderussisk"
|
||||
|
||||
#~ msgid "Bengali"
|
||||
#~ msgstr "Bengalsk"
|
||||
|
||||
#~ msgid "Greek"
|
||||
#~ msgstr "Græsk"
|
||||
|
||||
#~ msgid "English (Australia)"
|
||||
#~ msgstr "Engelsk (Australien)"
|
||||
|
||||
#~ msgid "English (Canada)"
|
||||
#~ msgstr "Engelsk (Canada)"
|
||||
|
||||
#~ msgid "English"
|
||||
#~ msgstr "Engelsk"
|
||||
|
||||
#~ msgid "Spanish"
|
||||
#~ msgstr "Spansk"
|
||||
|
||||
#~ msgid "English (United Kingdom)"
|
||||
#~ msgstr "Engelsk (Storbritannien)"
|
||||
|
||||
#~ msgid "Esperanto"
|
||||
#~ msgstr "Esperanto"
|
||||
|
||||
#~ msgid "Afrikaans"
|
||||
#~ msgstr "Afrikaans"
|
||||
|
||||
#~ msgid "Irish"
|
||||
#~ msgstr "Irsk"
|
||||
|
||||
#~ msgid "French"
|
||||
#~ msgstr "Fransk"
|
||||
|
||||
#~ msgid "Finnish"
|
||||
#~ msgstr "Finsk"
|
||||
|
||||
#~ msgid "Persian"
|
||||
#~ msgstr "Persisk"
|
||||
|
||||
#~ msgid "Croatian"
|
||||
#~ msgstr "Kroatisk"
|
||||
|
||||
#~ msgid "Indonesian"
|
||||
#~ msgstr "Indonesisk"
|
||||
|
||||
#~ msgid "Icelandic"
|
||||
#~ msgstr "Islandsk"
|
||||
|
||||
#~ msgid "Italian"
|
||||
#~ msgstr "Italiensk"
|
||||
|
||||
#~ msgid "Interlingua"
|
||||
#~ msgstr "Interlingua"
|
||||
|
||||
#~ msgid "Japanese"
|
||||
#~ msgstr "Japansk"
|
||||
|
||||
#~ msgid "Macedonian"
|
||||
#~ msgstr "Makedonsk"
|
||||
|
||||
#~ msgid "Korean"
|
||||
#~ msgstr "Koreansk"
|
||||
|
||||
#~ msgid "Latin"
|
||||
#~ msgstr "Latinsk"
|
||||
|
||||
#~ msgid "Kurdish"
|
||||
#~ msgstr "Kurdisk"
|
||||
|
||||
#~ msgid "Mongolian"
|
||||
#~ msgstr "Mongolsk"
|
||||
|
||||
#~ msgid "Polish"
|
||||
#~ msgstr "Polsk"
|
||||
|
||||
#~ msgid "Burmese"
|
||||
#~ msgstr "Burmesisk"
|
||||
|
||||
#~ msgid "Slovenian"
|
||||
#~ msgstr "Slovensk"
|
||||
|
||||
#~ msgid "Slovak"
|
||||
#~ msgstr "Slovakisk"
|
||||
|
||||
#~ msgid "Russian"
|
||||
#~ msgstr "Russisk"
|
||||
|
||||
#~ msgid "Portuguese"
|
||||
#~ msgstr "Portugisisk"
|
||||
|
||||
#~ msgid "Serbian"
|
||||
#~ msgstr "Serbisk"
|
||||
|
||||
#~ msgid "Albanian"
|
||||
#~ msgstr "Albansk"
|
||||
|
||||
#~ msgid "Traditional Chinese"
|
||||
#~ msgstr "Kinesisk (traditionel)"
|
||||
|
||||
#~ msgid "Simplified Chinese"
|
||||
#~ msgstr "Kinesisk (forenklet)"
|
||||
|
||||
#~ msgid "Vietnamese"
|
||||
#~ msgstr "Vietnamesisk"
|
||||
|
||||
#~ msgid "Chinese (Hong Kong)"
|
||||
#~ msgstr "Kinesisk (Hong Kong)"
|
||||
|
||||
#~ msgid "Chinese (Simplified)"
|
||||
#~ msgstr "Kinesisk (forenklet)"
|
||||
|
||||
#~ msgid "Chinese (Taiwan)"
|
||||
#~ msgstr "Kinesisk (Taiwan)"
|
||||
|
||||
#~ msgid "Ignore"
|
||||
#~ msgstr "Ignorer"
|
||||
|
||||
#~ msgid "Estonian"
|
||||
#~ msgstr "Estisk"
|
||||
|
||||
#~ msgid "Hebrew"
|
||||
#~ msgstr "Hebraisk"
|
||||
|
||||
#~ msgid "Hungarian"
|
||||
#~ msgstr "Ungarsk"
|
||||
|
||||
#~ msgid "Dutch"
|
||||
#~ msgstr "Hollandsk"
|
||||
|
8177
deluge/i18n/de.po
8177
deluge/i18n/de.po
File diff suppressed because it is too large
Load Diff
8244
deluge/i18n/el.po
8244
deluge/i18n/el.po
File diff suppressed because it is too large
Load Diff
8240
deluge/i18n/en_AU.po
8240
deluge/i18n/en_AU.po
File diff suppressed because it is too large
Load Diff
8268
deluge/i18n/en_CA.po
8268
deluge/i18n/en_CA.po
File diff suppressed because it is too large
Load Diff
1593
deluge/i18n/en_GB.po
1593
deluge/i18n/en_GB.po
File diff suppressed because it is too large
Load Diff
6757
deluge/i18n/eo.po
6757
deluge/i18n/eo.po
File diff suppressed because it is too large
Load Diff
1239
deluge/i18n/es.po
1239
deluge/i18n/es.po
File diff suppressed because it is too large
Load Diff
8225
deluge/i18n/et.po
8225
deluge/i18n/et.po
File diff suppressed because it is too large
Load Diff
7470
deluge/i18n/eu.po
7470
deluge/i18n/eu.po
File diff suppressed because it is too large
Load Diff
6981
deluge/i18n/fa.po
6981
deluge/i18n/fa.po
File diff suppressed because it is too large
Load Diff
2183
deluge/i18n/fi.po
2183
deluge/i18n/fi.po
File diff suppressed because it is too large
Load Diff
6164
deluge/i18n/fo.po
Normal file
6164
deluge/i18n/fo.po
Normal file
File diff suppressed because it is too large
Load Diff
2598
deluge/i18n/fr.po
2598
deluge/i18n/fr.po
File diff suppressed because it is too large
Load Diff
7674
deluge/i18n/fy.po
7674
deluge/i18n/fy.po
File diff suppressed because it is too large
Load Diff
6164
deluge/i18n/ga.po
Normal file
6164
deluge/i18n/ga.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1407
deluge/i18n/he.po
1407
deluge/i18n/he.po
File diff suppressed because it is too large
Load Diff
7935
deluge/i18n/hi.po
7935
deluge/i18n/hi.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1568
deluge/i18n/hu.po
1568
deluge/i18n/hu.po
File diff suppressed because it is too large
Load Diff
7078
deluge/i18n/id.po
7078
deluge/i18n/id.po
File diff suppressed because it is too large
Load Diff
8160
deluge/i18n/is.po
8160
deluge/i18n/is.po
File diff suppressed because it is too large
Load Diff
1651
deluge/i18n/it.po
1651
deluge/i18n/it.po
File diff suppressed because it is too large
Load Diff
6798
deluge/i18n/iu.po
6798
deluge/i18n/iu.po
File diff suppressed because it is too large
Load Diff
8009
deluge/i18n/ja.po
8009
deluge/i18n/ja.po
File diff suppressed because it is too large
Load Diff
7555
deluge/i18n/ka.po
7555
deluge/i18n/ka.po
File diff suppressed because it is too large
Load Diff
8077
deluge/i18n/kk.po
8077
deluge/i18n/kk.po
File diff suppressed because it is too large
Load Diff
6172
deluge/i18n/km.po
Normal file
6172
deluge/i18n/km.po
Normal file
File diff suppressed because it is too large
Load Diff
6962
deluge/i18n/kn.po
6962
deluge/i18n/kn.po
File diff suppressed because it is too large
Load Diff
8147
deluge/i18n/ko.po
8147
deluge/i18n/ko.po
File diff suppressed because it is too large
Load Diff
6774
deluge/i18n/ku.po
6774
deluge/i18n/ku.po
File diff suppressed because it is too large
Load Diff
6164
deluge/i18n/ky.po
Normal file
6164
deluge/i18n/ky.po
Normal file
File diff suppressed because it is too large
Load Diff
6755
deluge/i18n/la.po
6755
deluge/i18n/la.po
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,6 @@
|
||||
# This file is public domain.
|
||||
#
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
6164
deluge/i18n/lb.po
Normal file
6164
deluge/i18n/lb.po
Normal file
File diff suppressed because it is too large
Load Diff
8222
deluge/i18n/lt.po
8222
deluge/i18n/lt.po
File diff suppressed because it is too large
Load Diff
8283
deluge/i18n/lv.po
8283
deluge/i18n/lv.po
File diff suppressed because it is too large
Load Diff
7457
deluge/i18n/mk.po
7457
deluge/i18n/mk.po
File diff suppressed because it is too large
Load Diff
6164
deluge/i18n/ml.po
Normal file
6164
deluge/i18n/ml.po
Normal file
File diff suppressed because it is too large
Load Diff
8371
deluge/i18n/ms.po
8371
deluge/i18n/ms.po
File diff suppressed because it is too large
Load Diff
6172
deluge/i18n/nap.po
Normal file
6172
deluge/i18n/nap.po
Normal file
File diff suppressed because it is too large
Load Diff
8225
deluge/i18n/nb.po
8225
deluge/i18n/nb.po
File diff suppressed because it is too large
Load Diff
6755
deluge/i18n/nds.po
6755
deluge/i18n/nds.po
File diff suppressed because it is too large
Load Diff
8170
deluge/i18n/nl.po
8170
deluge/i18n/nl.po
File diff suppressed because it is too large
Load Diff
6180
deluge/i18n/nn.po
Normal file
6180
deluge/i18n/nn.po
Normal file
File diff suppressed because it is too large
Load Diff
6171
deluge/i18n/oc.po
Normal file
6171
deluge/i18n/oc.po
Normal file
File diff suppressed because it is too large
Load Diff
8294
deluge/i18n/pl.po
8294
deluge/i18n/pl.po
File diff suppressed because it is too large
Load Diff
6743
deluge/i18n/pms.po
6743
deluge/i18n/pms.po
File diff suppressed because it is too large
Load Diff
1666
deluge/i18n/pt.po
1666
deluge/i18n/pt.po
File diff suppressed because it is too large
Load Diff
1695
deluge/i18n/pt_BR.po
1695
deluge/i18n/pt_BR.po
File diff suppressed because it is too large
Load Diff
8180
deluge/i18n/ro.po
8180
deluge/i18n/ro.po
File diff suppressed because it is too large
Load Diff
1709
deluge/i18n/ru.po
1709
deluge/i18n/ru.po
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user