Compare commits

...

772 commits

Author SHA1 Message Date
zakary
0b5addf58e
[UI] Convert all ico to png format for tracker icon
Some checks failed
Package / windows_package (x64, 1.2.19, 3.9) (push) Has been cancelled
Package / windows_package (x64, 2.0.7, 3.9) (push) Has been cancelled
Package / windows_package (x86, 1.2.19, 3.9) (push) Has been cancelled
Package / windows_package (x86, 2.0.7, 3.9) (push) Has been cancelled
CI / test-linux (3.10) (push) Has been cancelled
CI / test-linux (3.7) (push) Has been cancelled
CI / test-windows (3.10) (push) Has been cancelled
CI / test-windows (3.7) (push) Has been cancelled
Docs / build (push) Has been cancelled
Linting / lint (push) Has been cancelled
Closes: https://github.com/deluge-torrent/deluge/pull/472
2025-03-01 21:11:17 +00:00
garret
98d01fbe35
[UI] Split trackers by newline, not by "/n"
Fixes a bug where e.g. http://not-a-real-tracker.com was split into
http:/
ot-a-real-tracker.com
when the add trackers dialogue was next shown

Closes: https://github.com/deluge-torrent/deluge/pull/469
2025-03-01 21:10:50 +00:00
DjLegolas
ee33c0c5bb
[3350][GTK] Fix a regression with quick search
When one tries to search in the TorrentView, then a little search window
appears but does nothing.

Ref: https://lazka.github.io/pgi-docs/Gtk-3.0/callbacks.html#Gtk.TreeViewSearchEqualFunc
Closes: https://dev.deluge-torrent.org/ticket/3350
Closes: https://github.com/deluge-torrent/deluge/pull/467
2025-03-01 21:09:21 +00:00
Martin Hertz
0e197ee07e
[Console] Fix 'rm' command hanging when done
Commander.exec_command was await'ing returned deferreds as per commit 253eb22.

Closes: https://github.com/deluge-torrent/deluge/pull/464
2025-03-01 21:08:44 +00:00
Calum Lind
e83f6b84fb
[Tests] Torrent error status xfail for lt>2.0.7
Some checks failed
Package / windows_package (x64, 1.2.19, 3.9) (push) Has been cancelled
Package / windows_package (x64, 2.0.7, 3.9) (push) Has been cancelled
Package / windows_package (x86, 1.2.19, 3.9) (push) Has been cancelled
Package / windows_package (x86, 2.0.7, 3.9) (push) Has been cancelled
CI / test-linux (3.10) (push) Has been cancelled
CI / test-linux (3.7) (push) Has been cancelled
CI / test-windows (3.10) (push) Has been cancelled
CI / test-windows (3.7) (push) Has been cancelled
Docs / build (push) Has been cancelled
Linting / lint (push) Has been cancelled
Something changes with libtorrent 2.0.7 with how it handles error state
with missing pieces on disk.

This will require further investigation but will mark the tests as xfail
for now.
2025-02-18 18:29:01 +00:00
Calum Lind
0878616b2e
[Tests] Fix stalling Console tests due to fixture mro order change
The Console UI tests started stalling after the realese of pytest 7.4.3
which was a result of the reversing the mro order of fixtures on
classes, to give base classes priority.

The change resulted in `base_fixture` being sorted before `client` and
`daemon` fixtures and the tests stalled, likely due to the use of yield
in base_fixture.

    # pytest 7.4.2
    [Mark(name='usefixtures', args=('client', 'daemon'), kwargs={})],
    [Mark(name='usefixtures', args=('base_fixture',), kwargs={})],
    # pytest 7.4.3
    [Mark(name='usefixtures', args=('base_fixture',), kwargs={})],
    [Mark(name='usefixtures', args=('client', 'daemon'), kwargs={})],

The fix is to move base_fixture along with the other fixtures and place it
last.

Refs: https://github.com/pytest-dev/pytest/pull/11545
2025-02-18 18:27:15 +00:00
Calum Lind
7c5b7b44a3
[Tests] Remove duplicate daemon setup for Console UI tests
The tests setup for TestConsoleScriptEntryWithDaemon uses a daemon
fixture so remove the extra DaemonBase setup
2025-02-18 18:27:11 +00:00
Calum Lind
7071da85c3
[Tests] Use async for ConsoleUI tests 2025-02-17 22:38:34 +00:00
Calum Lind
cb182daaaf
[Common] Fix Twisted 24.x no longer calling addCallbacks in Deferred subclass
Some checks failed
Package / windows_package (x64, 1.2.19, 3.9) (push) Has been cancelled
Package / windows_package (x64, 2.0.7, 3.9) (push) Has been cancelled
Package / windows_package (x86, 1.2.19, 3.9) (push) Has been cancelled
Package / windows_package (x86, 2.0.7, 3.9) (push) Has been cancelled
CI / test-linux (3.10) (push) Has been cancelled
CI / test-linux (3.7) (push) Has been cancelled
CI / test-windows (3.10) (push) Has been cancelled
CI / test-windows (3.7) (push) Has been cancelled
Docs / build (push) Has been cancelled
Linting / lint (push) Has been cancelled
A change to Deferred logic in 24.10 means that `addCallbacks` is no
longer being called in Deferred methods. This is a problem since our
subclass of Deferred for coroutines was using that mechanism to check
awaited status.

Fixed by extending each method that requires awaited status check.

Reference: https://github.com/twisted/twisted/pull/12283
Reference: 4d04b64b77
2025-02-10 13:24:52 +00:00
Niluge_KiWi
8df36c454b
[WebUI] Accept network interface name as well as IP address
Deluge & libtorrent actually accept both IP address and device/interface namesgq
as listen_interface: 540d557c which patched ui/ console & gkt3, but not
web, this fixes it.

(Inspired by https://github.com/deluge-torrent/deluge/pull/300)

(Translation should be ok: this string already exists)

Also, resync listen & outgoing widths: they hold the same data types.

Closes: https://github.com/deluge-torrent/deluge/pull/458
2024-09-10 19:04:37 +01:00
Martin Hertz
40d4f7efef
[Console] Fix 'move' command hanging when done
Console.get_torrent_name() expects string, but was given list from console.match_torrent(), so NoneType breaking join() in move message.

Console.get_torrent_name() expects string, but was given list from console.match_torrent(), so NoneType breaking join() in move message. Simplified and fixed now.

Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
Closes: https://github.com/deluge-torrent/deluge/pull/447
2024-09-10 19:00:41 +01:00
Calum Lind
d064ad06c5
[Docs] Replace black/flake8 with pre-commit 2024-09-08 18:32:06 +01:00
Calum Lind
0d72195281
[Lint] Format code with ruff
`pre-commit run --all-files`
2024-09-08 17:51:43 +01:00
Calum Lind
2247668571
[Lint] Fix ruff lint gettext and type comparison
The gettext strings cannot be formatted until after the function return
from gettext.

Refs: https://docs.astral.sh/ruff/rules/f-string-in-get-text-func-call/
Refs: https://docs.astral.sh/ruff/rules/printf-in-get-text-func-call/
2024-09-08 17:48:43 +01:00
Calum Lind
e7d08d7645
[Typing] Add pyright config to suppress warnings
VSCode uses pylance/pyright, a performant type checker. So
setup the builtins used by Deluge and set missing imports to
informational due to OS-specific imports.

It might be possible using extraPaths with extra stubs or use
defineConstants to make pyright not check Windows or Macos conditional
paths but that would require changing all usage so leaving for another
time.

Refs: https://github.com/microsoft/pyright/blob/main/docs/configuration.md
Refs: https://github.com/microsoft/pyright/blob/main/docs/builtins.md
2024-09-08 17:48:05 +01:00
Calum Lind
90c5e75373
[Lint] Replace black/flake8/isort with ruff
Use ruff as a single performant tool to lint and format Python code.
2024-09-08 17:46:40 +01:00
Martin Hertz
c88f750108
[Console] Block interactive-mode on Windows even with windows-curses
Testing with window-curses results in hangs on initial loading with background error:

    File "C:\Users\Docker\Deluge\.venv\lib\site-packages\twisted\internet\selectreactor.py", line 39, in win32select
        r, w, e = select.select(r, w, w, timeout)
    builtins.OSError: [WinError 10038] An operation was attempted on something that is not a socket

This is due to passing a Console class to addReader but this fails since
select on Windows cannot handle non-socket file object unlike Unix which accepts
sockets and file objects.

There is likely a further issue where windows-curses has not implemented
resizeterm so would need to use resize_term instead.

Refs: https://docs.python.org/3/library/select.html#select.select
Refs: https://stackoverflow.com/questions/11731175/python-twisted-addreader-works-in-linux-but-not-windows
Refs: https://github.com/zephyrproject-rtos/windows-curses/issues/40
Refs: https://docs.python.org/3/library/curses.html#curses.resize_term

Closes: https://github.com/deluge-torrent/deluge/pull/457
Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
2024-09-08 10:04:42 +01:00
Calum Lind
491458c4ad
[CI] Fix accidental revert of Twisted pin for Windows
In commit 9d802b2 I pushed a change to tests which included a revert of
Windows pinned dependencies which was an accident. The actual change
should only have reverted setup.py pinned dependency since we down want
a release to not be able to use a later fixed version of Twisted.
2024-09-08 09:49:18 +01:00
Mamoru TASAKA
5d96cfc72f
[UI] Replace deprecated cgi module with email
As PEP 594 says, cgi module is marked as deprecated
in python 3.11, and will be removed in 3.13
(actually removed at least in 3.13 rc1).

As suggested on PEP 594, replace cgi.parse_header
with email.message.EmailMessage introduced in python 3.6.

Updated test modify test_download_with_rename_sanitised
- With RFC2045 specification, Content-Disposition filenames
parameter containing slash (directory separator) must be
quoted, so changing as such.

Ref: https://peps.python.org/pep-0594/#deprecated-modules
Ref: https://peps.python.org/pep-0594/#cgi

Closes: https://github.com/deluge-torrent/deluge/pull/462
2024-09-08 09:45:14 +01:00
zakary
3bceb4bfc1
[UI][Common] Wrap torrent comment and tracker status URLs in HTML (clickable)
Closes: https://github.com/deluge-torrent/deluge/pull/460
2024-09-08 09:45:14 +01:00
Calum Lind
9d802b2a91
[Tests] Fix missing __qualname__ for mock callback
test_pop_alerts raised the following error:

    File "/home/runner/work/deluge/deluge/deluge/core/alertmanager.py", line 177, in handle_alerts
        handler=handler.__qualname__,
    File "lib/python3.10/unittest/mock.py", line 645, in __getattr__
        raise AttributeError(name)
    AttributeError: __qualname__

Mocks don't generate dunder methods like `__qualname__` attribute so
we need to manually specify it.
2024-09-06 21:02:50 +01:00
Gregorio Litenstein
8867da94f8
[Console] endwin() not needed when using wrapper
Using it anyway produces a crash because it returns an error if called two times without any intervening updates.

Closes: https://github.com/deluge-torrent/deluge/pull/461
2024-08-26 20:48:38 +01:00
Martin Hertz
e1fa8d18ec
[Console] Improve interactive-mode preferences saving
The bottom options for cancel/apply/ok where confusing for end-users as being checkboxes needing spacebar prepended to activate firstly, before return/enter to activate said previous selection, but changed now to omit. Also fixed not showing canceled options as sticking.

Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
Closes: https://github.com/deluge-torrent/deluge/pull/445
2024-08-26 19:43:11 +01:00
kenstir
d5af32802f
[Build] Fix Ubuntu dependency for appindicator
Change name to gir1.2-appindicator3-0.1.  Tested on Ubuntu 22.04.4 LTS
and Ubuntu 20.04.6 LTS.
Replace appindicator3 refs with newer ayatanaappindicator3
Replace broken python-appindicator link with working jammy link.

Closes: https://github.com/deluge-torrent/deluge/pull/459
2024-08-26 19:39:59 +01:00
Calum Lind
d1d72b1be8
[UI] Replace deprecated Pillow Image.Antialias with Lanczos
ANTIALIAS was removed in Pillow 10.0.0 so replace with suggested
alternative.

Ref: https://pillow.readthedocs.io/en/stable/releasenotes/10.0.0.html#constants
2024-08-26 19:36:53 +01:00
Calum Lind
776efe4faa
[i18n] Update po files from launchpad 2024-08-24 23:14:49 +01:00
Jacob Siverskog
f101f0afdd
[Console] Fix ports typo
incomming -> incoming.

Closes: https://github.com/deluge-torrent/deluge/pull/442
2024-08-24 17:40:12 +01:00
Martin Hertz
d98d15422a
[AutoAdd] Fix Windows display scaling breaking WebUI elements
Closes: https://github.com/deluge-torrent/deluge/pull/444
2024-08-24 17:35:31 +01:00
zakary
d9e3facbe8
[CI] Update actions to latest version (node 16 deprecation)
Closes: https://github.com/deluge-torrent/deluge/pull/453
2024-08-24 17:33:18 +01:00
Martin Hertz
6ba23a8013
[Packaging] NSIS x64 reg-keys into proper place
Closes: https://github.com/deluge-torrent/deluge/pull/455
2024-08-24 16:13:36 +01:00
Calum Lind
af70ff1fdc
[Util] Refactor reset language env vars 2024-08-24 13:52:24 +01:00
DjLegolas
18fa028d2d
[3635][WebUI] fix language change to system default
When setting a language, we set 2 environment variables at run time.
Setting the language back to `System Default`, we don't clean those env
variables.
In the WebUI, the page only reloads, and we still use the env variables
to set the language back to the previous one.
This does not affect GTK.

Closes: https://dev.deluge-torrent.org/ticket/3635
Closes: https://github.com/deluge-torrent/deluge/pull/450
2024-08-24 13:42:13 +01:00
DjLegolas
322faa7a54
[Build] Prevent usage of twisted>=23 on Windows
with newer versions of twisted, a regression was added for the GTK
reactor on Windows.
it effects all versions, including latest (currently 24.3.0).

So will prevent the upgrade on Windows only.

Issue: https://dev.deluge-torrent.org/ticket/3634
Related: https://github.com/twisted/twisted/issues/11987
Closes: https://github.com/deluge-torrent/deluge/pull/448
2024-08-24 11:34:16 +01:00
Calum Lind
785ad00d2b
[WebUI] Fix gettext _ imports 2024-08-24 09:23:22 +01:00
Calum Lind
1e5f248fb8
[WebUI] Fix error stopping daemon in connection manager
The wrong number of arguments was being parsed from get_host_info.
2024-08-24 09:23:22 +01:00
Calum Lind
80985c02da
[CI] Disable failing alertmanager test on Windows
The test_pause_not_pop_alert test passes on Linux but is consistently failing
in CI pipeline for Windows:

    AssertionError: Expected 'mock' to not have been called. Called 1 times.
    Calls: [call.deferred.cancel(),
    call(LtAlertMock(type=1, name='mock_alert1', message='Alert 1'))].

Disabling the test until it can be resolved.
2024-08-19 16:30:12 +01:00
Calum Lind
7660e2e5ca
Add deluge.pot to repo for new translation app
Attempting to move to weblate but need deluge.pot in the repo
2024-07-15 08:39:31 +01:00
Calum Lind
7f3f7f69ee
[GtkUI] Reword preferences to prefer dark theme
The GTK option for dark theme only provides a preference to use the dark
theme, there is not a similar option for light theme so the settings
should only refer to 'prefer dark theme' and whether it's on or off.
2024-02-19 17:50:37 +00:00
Calum Lind
5dd7aa5321
[WebUI] Refactor changing theme
Simplify searching for themes and ensure theme is ordered last.

Ideally themes would be set client-side but seems to be quite tricky to
accomplish with ExtJS.
2024-02-19 17:50:32 +00:00
DjLegolas
ee97864086
[GtkUI] Add a way to change themes
Currently, the only way to change the themes is by manually set a value
in the command line or set it as env variable.

Closes: https://dev.deluge-torrent.org/ticket/3536
Closes: https://github.com/deluge-torrent/deluge/pull/392
2024-02-19 17:49:04 +00:00
DjLegolas
848d668af9
[WebUI] Add a way to change themes
Currently, the only way to change the themes is by manually set a value
in the `web.conf` file itself.

Closes: https://dev.deluge-torrent.org/ticket/3536
2024-02-19 15:54:57 +00:00
Chris Ross
d9ef65d745
[WebUI] Fix tracker icon to fit within tracker column rows
For me at least, Safari on Mac OS X, the tracker icon significantly
overflows in the Tracker column of the torrent list. (It does show the
correct size in the Trackers filter, though. Different CSS.)

This change causes it to constrain down to the height of the column and
display correctly.

Closes: https://github.com/deluge-torrent/deluge/pull/440
2024-01-21 15:20:24 +00:00
freddy2659
7f70d6c6ff
[WebUI] Fix progress divide by 0 error with empty dir
If a dir exists with no contents then the following error occurred:

```
Traceback (most recent call last):
  ...
  File "/usr/lib/python3.10/site-packages/deluge/ui/web/json_api.py", line 608, in _on_got_files
    dirinfo['progress'] = sum(progresses) / dirinfo['size'] * 100
builtins.ZeroDivisionError: float division by zero
```

Closes: https://github.com/deluge-torrent/deluge/pull/439
2024-01-21 15:18:29 +00:00
Calum Lind
b7450b5082
[Docs] Bump sphinx version requirements
There are still warnings that need to be resolved but the build is
passing.
2024-01-21 15:10:47 +00:00
Calum Lind
7046824115
[Alerts] Fix alert handler segfault on lt.pop_alerts
We cannot handle an alert after calling lt.pop_alerts for a subsequent
time since the alert objects are invalidated and with cause a segfault.

To resolve this issue add a timeout to the handler calls and wait in the
alert thread for either the handlers to be called or eventually be
cancelled before getting more alerts.

This is still not an ideal solution and might leave to backlog of alerts
but this is better than crashing the application. Perhaps the timeout
could be tweaked to be shorter for certain alert types such as stats.

Related: https://github.com/arvidn/libtorrent/issues/6437
2024-01-21 14:16:22 +00:00
Calum Lind
fa8d19335e
[GTK3] Fix changing torrent ownership
The change ownership menu item was broken due to Gtk deprecation
changes in commit #bcaaeac.

Fixed by correctly setting the RadioMenuItem group

Refactored to simplify the code
Removed dead or unneeded code

Fixes: https://dev.deluge-torrent.org/ticket/3610
2023-12-02 21:20:22 +00:00
Calum Lind
0c1a02dcb5
[Core] Remove usage of deprecated torrent status.paused
Noticed mismatch with current lt docs and found usage of deprecated
status.paused.

The actual check here is not required we should just attempt to pause
the torrent.

Issue: https://dev.deluge-torrent.org/ticket/3499
2023-12-02 21:20:11 +00:00
Calum Lind
810751d72a
[Tests] Refactor parse_human_size test 2023-11-30 19:03:40 +00:00
Calum Lind
7199805c89
[Common] Fix order of size_units for parse_human_size
The previous commit changed the order of the size
2023-11-27 16:52:03 +00:00
Arkadiusz Bulski
29cf72577f
[Common] Add extra size units suffixes for user input
Minor updates to docstrings

Closes: https://github.com/deluge-torrent/deluge/pull/437
2023-11-27 15:58:19 +00:00
Calum Lind
42accef295
[Tests] Fix unhandled ProcessTerminated trying to kill daemon in clean
The GitHub CI tests on Linux were failing due to ProcessTerminated

>       await daemon.kill()
E       twisted.internet.error.ProcessTerminated: A process has ended with a probable error condition: process ended by signal 6.

Added a try..except in daemon as a quick solution but might need to
investigate the ProcessOutputHandler.kill method.
2023-11-27 15:48:24 +00:00
Calum Lind
54d6f50231
[Core] Refactor Alertmanager to retry unhandled alerts
We are currently creating a copy of each alert to avoid segfaults when
the next pop_alerts invalidates the lt alert objects we are holding
for handler callbacks.

However these alert copies are not deep enough since need to also
resolve the alert object methods e.g. message() therefore it is still
possible to result in lt segfaults.

The workaround is to check for any handlers not called, give them some
more time and eventually discard if still not handled.

Ref: https://github.com/arvidn/libtorrent/issues/6437
2023-11-27 15:17:44 +00:00
DjLegolas
b5f8c5af2d
[Core] Call wait_for_alert in a thread
This spawns a thread in alertmanager to call wait_for_alert in a thread.
This reduces latency to deluge responding to events.
And removes all `hasattr` checks in Component

Closes: https://github.com/deluge-torrent/deluge/pull/221
2023-11-27 15:00:56 +00:00
Calum Lind
c7dc60571e
[CI] Fix windows packaging build
With recent update to pyinstaller 6.0 the libraries are now placed in an
`_internal` folder within the bundle. This has resulted in the failure
to create copies of libssl.

However after examining the new _internal dir it appears that the x64
lib are now created so this step is no longer required.
2023-11-24 20:50:15 +00:00
DjLegolas
1989d0de73
[UI][Common] Add daemon version check
For new UI features to be added, one should make sure the backend daemon
is supported and add fallback in case it doesn't.
Here we add the ability to get the daemon version from the `Client`
class and also check the version against a desired version.

Closes: https://github.com/deluge-torrent/deluge/pull/427
2023-11-20 12:36:52 +00:00
Radu Carpa
1751d62df9
[Core] Support creating v2 torrents
Add support for v2 torrents in create_torrent, but keep the old
default of only adding the v1 metadata.

Unify the single-file and directory cases to avoid code
duplication.

V2 torrents require files to be piece-aligned. The same for
hybrid v1/v2 ones. To handle both cases of piece-aligned and
non-aligned files, always read the files in piece-aligned
chunks. Re-slice the buffer if needed (for v1-only multi-file
torrents).

Also, had to adapt to progress event. It now depends on the
number of bytes hashed rather than the number of pieces. To
avoid sending and excessive amount of event when handling a
directory with many small files, add a mechanism to limit
event period at 1 per piece_length.

Closes: https://github.com/deluge-torrent/deluge/pull/430
2023-11-20 10:05:39 +00:00
Radu Carpa
4088e13905
[Core] Make create_torrent return a deferred
This allows to create a torrent file on the remote server
and get its content in one call.
2023-11-20 10:05:33 +00:00
Radu Carpa
b63699c6de
[Core] Don't always write create_torrent metafile to disk
If target=None and add_to_session is True, the torrent will be
directly added to the session without writing the torrent file
to disk. This will allow to programmatically control deluge via
RPC without creating .torrent files all over the place.

Also, make most create_torrent parameters optional.
2023-11-20 10:05:33 +00:00
Calum Lind
8dba0efa85
[Lint] Fixup files 2023-11-19 23:14:44 +00:00
Calum Lind
b2005ecd78
[Tests] Fix console tests stalling by pinning pytest to 7.4.2
Tests were stalling in deluge_ui_entry with pytest 7.4.3 likely due to
changes in the way it handles stderr but it is not clean the extact
issue.

For now we will pin the pytest version and look to fix the issue later.

Reference: https://docs.pytest.org/en/stable/changelog.html#pytest-7-4-3-2023-10-24
2023-11-19 23:14:44 +00:00
Calum Lind
39b99182ba
[UI] Further compress Deluge icons 2023-11-19 19:15:29 +00:00
Calum Lind
66eaea0059
[Scripts] Replace icon bash script with python version
Preferable to be working with Python scripts for easier development.

This is direct port of the original bash script with the slight
modification to also use pngquant to reduce file size further.
2023-11-19 19:15:29 +00:00
Calum Lind
5aa4d07816
[Stats] Fix creating test artifacts in tmp not pwd 2023-11-19 19:15:28 +00:00
Calum Lind
f3d7b1ffe8
[UI] Minor improvements to create_deluge_pngs
Change the default compression tool to oxipng since it produces smaller
files faster than zopfli.

Add the ECT tool as an option to get maximum compression but is quite
slow to complete.
2023-11-19 19:15:28 +00:00
Anthony Ryan
d8f9fe4acf
[UI] Losslessly compress PNG images
Using Efficient-Compression-Tool, we managed to reduge image sizes by
Saved 9.45KB out of 241.88KB (3.9065%) without changing visual appearence.

It's a small improvement, but we get something for nothing and that's nice.

    ect -keep --allfilters-b --pal_sort=120 -30060 -recurse --mt-file ./

Closes: https://github.com/deluge-torrent/deluge/pull/405
2023-11-19 19:15:17 +00:00
Calum Lind
f43b605b80
[i18n] Update translations from launchpad 2023-11-13 12:10:39 +00:00
Sébastien Luttringer
1dbb18b80a
Fix tweaking logging levels
When you set a config directory path (-c) on the command line, and only when,
a file named logging.conf is read to set fine grained log levels.
This allows to have per module/plugin log levels.

A simple logging.conf could be:
-<8 -------------------
deluge:info
deluge.plugin.foo:debug
-----------------------

The file is parsed and the log levels are set in the deluge.log.tweak_logging_levels.
This function set the appropriate logger to the desired level.
Despite the log level is changed, log levels less than ERROR are still not
logged.

The reason is that the log level check is done twice, in the logging.Logger class
and in the logging.Handler class.

The fix is to not set the logging level in the Handler in deluge.log.setup_logger
and let only the logging.Logger check the level.

Closes: https://github.com/deluge-torrent/deluge/pull/428
2023-09-18 20:04:14 +01:00
Myrmecophaga tridactyla
21470799d0
i18n: Fix language word
Closes: https://github.com/deluge-torrent/deluge/pull/429
2023-09-18 20:02:39 +01:00
Calum Lind
e24081a17e
[CI] Bump Pyinstaller version for Windows packaging
Pyinstaller was pinned due to problems with v5 but these should now be
resolved with latest version.

Bump Twisted to latest minor version

Closes: https://github.com/deluge-torrent/deluge/pull/435
2023-09-18 19:56:31 +01:00
Calum Lind
6c9b058d81
[CI/CD] Fix Pillow packaging build errors
Pillow have dropped 32-bit wheels from v10 onwards so force install of
older v9 wheels since we don't want to build from src.

Refs: https://github.com/python-pillow/Pillow/issues/6941#issuecomment-1604058002
2023-09-18 14:55:53 +01:00
Calum Lind
18dca70084
[GTKUI] Fix cairo crashes by not storing current context
Windows users have reported Deluge crashes when resizing the window with
Piecesbar or Stats plugins enabled:

    Expression: CAIRO_REFERENCE_COUNT_HAS_REFERENCE(&surface->ref_count)

This is similar to issues fixed in GNU Radio which is a problem due to
storing the current cairo context which is then being destroyed and
recreated within GTK causing a reference count error

Fixes: https://dev.deluge-torrent.org/ticket/3339
Refs: https://github.com/gnuradio/gnuradio/pull/6352
Closes: https://github.com/deluge-torrent/deluge/pull/431
2023-09-18 14:55:38 +01:00
Calum Lind
ed1366d5ce
[Docs] Fix readthedocs builds
Requires using updated configuration with git unshallow so that version
can be identified from git tags.

Closes: https://github.com/deluge-torrent/deluge/pull/434
2023-09-18 13:00:59 +01:00
Calum Lind
7082d9cec4
[CI] Fix packaging errors with Python 3.7
The latest Pillow 10 does not support Py3.7 therefore wheels are no
longer available and we need to specify previous major version.

Older versions of setuptools do not correctly determine the Twisted
requirement for zope.interface>5 on Python 3.7 so ensure latest
installed. For the CD builds we don't want any surprises so keep the
setuptools version pinned.

Refs: https://pillow.readthedocs.io/en/stable/installation.html
Closes: https://github.com/deluge-torrent/deluge/pull/433
2023-09-18 13:00:43 +01:00
Calum Lind
015b0660be
[CI] Remove chardet version constraint
The version was pinned due to issues with Python 3.10.5 in CI but with
latest versions of Python 3.10 this no longer seems to be an issue.
2023-09-18 11:37:04 +01:00
DjLegolas
a459e78268
[UI][Common] Add support for BitTorrent V2 file tree
In BEP52, a new tiles structure was introduce, a `file tree`.
This change added support for this structure in the `TorrentInfo` class.

Ref: https://www.bittorrent.org/beps/bep_0052.html
Closes: https://github.com/deluge-torrent/deluge/pull/404
2023-08-27 11:35:43 +01:00
doadin
8001110625
[Packaging] Fix NSIS Uninstaller Not Removing File\Folder
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Website.lnk" "$INSTDIR\homepage.url"

gets made in the installer but the uninstaller was looking for

 Delete "$SMPROGRAMS\$StartMenuFolder\Deluge Website.lnk"

therefore this file was being left behind and since the folder was not empty

$StartMenuFolder\Deluge

was being left behinde as well.

Closes: https://github.com/deluge-torrent/deluge/pull/426
2023-05-30 19:50:23 +01:00
Calum Lind
d8b586e6ba
[CI] Bump image and action versions
Updated the core dump storage to be enabled only when needed.
Fixed missing catchsegv on Ubuntu 22.04 by installing glibc-tools
2023-05-30 19:45:57 +01:00
Calum Lind
905a7dc3bc
[Tests] Only pin libtorrent for tests
Bump versions for Windows packaging
2023-05-30 17:11:13 +01:00
bendikro
89b79e4b7f
[Core] Fix bug when emiting event in EventManager
In some cases, when emiting events with EventManager.emit_event(), the underlying
dictionary of interested sessions can change during iteration, causing:
RuntimeError: dictionary changed size during iteration

Fix by iterating over a copy of the dictionary.

Closes: https://dev.deluge-torrent.org/ticket/3351
Closes: https://github.com/deluge-torrent/deluge/pull/425
2023-05-29 13:57:02 +01:00
bendikro
e70e43e631
[GTKUI] Add torrent dialog incorrectly removes dir
When adding a torrent in the add torrents dialog, containing only
a single directory with a single file inside, the directory is not
included as a prefix to the filename.
The prefix is added only if there are multiple files inside the directory.

Fix by adding the prefix also when there is only one file inside a dir.

Closes: https://dev.deluge-torrent.org/ticket/3602
Closes: https://github.com/deluge-torrent/deluge/pull/424
2023-05-29 13:15:43 +01:00
Calum Lind
b24a5d2465
[Tests] Ignore pytest temp dir 2023-05-29 12:10:47 +01:00
Calum Lind
701f68d70b
[Tests] Pin libtorrent version to 2.0.7
With latest libtorrent 2.0.9 testing are erroring out so pin
to older working version.

Ref: https://dev.deluge-torrent.org/ticket/3603
2023-05-29 12:10:00 +01:00
Calum Lind
de570ae536
[Tests] Fix waiting for lt alert state change
Tests are fragile when waiting for lt alert so allow incrementally
waiting for status change, along with a timeout.
2023-05-29 12:08:39 +01:00
Calum Lind
40a66278a3
[Common] Replace pkg_resources with importlib for resource path finding
With an existing Deluge package installed on the system errors were
occuring trying to start a development instance in virtualenv.

Fixed by replacing usage of deprecated pkg_resource for finding
non-python data files. Includes fallback for Python 3.7/3.8 but drops
Python 3.6 support.

The plugins are still using pkg_resources since they are distributed as
eggs and importlib extracts those data files differently to
pkg_resources so requires a different solution, either as a file stream
or manually cached when plugins are installed.

Closes: https://github.com/deluge-torrent/deluge/pull/403

Co-authored-by: DjLegolas <djlegolas@protonmail.com>
2023-05-29 12:08:34 +01:00
Unit 193
366cded7be
[GTKUI] Enable appindicator support by default
On GNOME, I don't believe any tray icon or indicator support is enabled
by default, but one can easily install an extension for indicators
whereas I'm not sure about tray icons, but the 'top icons' extension I
believe has had a flaky history. I'm not entirely sure how KDE handles
things, but out of the box it too has indicator support and I believe
that is preferred with the move to Wayland. In Xfce, the tray icon area
is called "Status tray" and has support for both tray icons and
indicators.

Closes: https://github.com/deluge-torrent/deluge/pull/318
2023-04-23 18:15:52 +01:00
Calum Lind
dbedf7f639
[GTK3] Fix missing AppIndicator option in Preferences
Issue with legacy tray icon being used despite AyatanaAppIndicator3
installed. The problem is due to option to use AppIndicator not being
shown in Preferences.

Fixes: https://dev.deluge-torrent.org/ticket/3598
2023-04-23 18:15:52 +01:00
Xuefer
81116a63ca
[WebUI] use rational maxValue
Signed-off-by: Xuefer <xuefer@gmail.com>
Closes: https://github.com/deluge-torrent/deluge/pull/422
2023-04-23 18:15:51 +01:00
Xuefer H
a83ac65ab6
[WebUI] Sidebar: fix error for lazy init
When sidebar is hidden at WebUI startup, header isn't created yet.

Signed-off-by: Xuefer H <xuefer@gmail.com>
Closes: https://github.com/deluge-torrent/deluge/pull/419
2023-04-23 17:30:55 +01:00
Xuefer H
d2a56ce15e
[WebUI] FilesTab: undefined setColumnValue
setColumnValue was removed

Signed-off-by: Xuefer H <xuefer@gmail.com>
Closes: https://github.com/deluge-torrent/deluge/pull/417
2023-04-23 17:29:45 +01:00
Xuefer H
71b634e968
[WebUI] use setTimeout instead of setInterval
When server is busy or the request is slow for big file list, WebUI still
requests for new update blindly. "Connection lost" is often triggerd.

Change to only ask for update 2s after reponse (either success or error)

Signed-off-by: Xuefer H <xuefer@gmail.com>
Closes: https://github.com/deluge-torrent/deluge/pull/416
2023-04-23 17:29:06 +01:00
Calum Lind
39bd97f03e
[Core] Fix getting libtorrent alert type
Encountered a problem with dht_error alert not returning the correct
alert name using Python type.

This should likely be fixed in libtorrent but we should be using
the alert.what method to determine alert type/name.

Since the alert name does not include the `_alert` suffix, strip this
when registering alerts.
2023-03-07 16:18:50 +00:00
Calum Lind
196086c1fb
[Core] Refactor alert handler for readability 2023-03-07 16:05:17 +00:00
Chase Sterling
527cfa586c
[Tests] Autorun async tests with pytest_twisted.
Since all of our async tests should be run with twisted, it's
annoying to have to decorate them with pytest_twisted.ensureDeferred.
Forgetting to do this will cause the test to pass without actually
running. This changes the behavior to detect all coroutine function
tests and mark them to be run by pytest_twisted the same way the
decorator does.

Closes: https://github.com/deluge-torrent/deluge/pull/414
2023-03-07 09:24:46 +00:00
DjLegolas
25a2b113e2
[Component] Add pause and resume events methods
For future use add ability to handle pause and resume events

Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
2023-03-06 13:25:18 +00:00
Calum Lind
c38b4c72d0
[Tests] Refactor component tests for readability
Modified test functions to be async.

Used pytest_twisted_ensuredeferred_for_class decorator to avoid needed
ensureDeferred for each test within the class. There might be a way to
do this with fixtures so likely to be improvements for use in all test
classes.

Used Mock in component subclass for simpler tracking of event method calls
2023-03-06 13:25:00 +00:00
DjLegolas
0745c0eff8
[Component] Refactor to remove hasattr usage
Functions are defined in the class now

https://github.com/deluge-torrent/deluge/pull/221#discussion_r228275050
2023-03-06 13:20:14 +00:00
Calum Lind
e90f6c7eef
[Tests] Use tmp_path for test logfile
Daemon logfile were being stored in dir where tests were started from
which cluttered up local dev env.

Entry point logfiles are stored in config dir since getting tmp_path in
set_up was a bit too tricky.
2023-03-03 15:57:46 +00:00
Calum Lind
7b1a0ef89c
[Tests] Fix error in TestJSONRequestFailed test
Fix test generating following error

    'TestJSONRequestFailed' object has no attribute 'core'
2023-03-03 15:18:47 +00:00
Calum Lind
75b27485e1
Add metainfo.xml to gitignore 2023-03-03 10:46:31 +00:00
Calum Lind
a64cdfaf78
[GtkUI] Enable remapping of keyboard shortcuts
With key shortcuts set in the glade files it is not easy to change
the shortcuts on macOS.

Add AccelPaths to each MenuItem to allow adding or modifying accelerator
in codes. Uses the recommended format `<Deluge-MainWindow>/MenuName`

Update menubar code to use new functionality, taking advantage of
accelerator_parse to specify accelerators as strings instead of Gdk
flags.

A future idea would be to use `Gtk.AccelMap.load` to have shortcuts
loaded from user config.

Co-authored-by: Gregorio Litenstein <g.litenstein@gmail.com>
2023-03-03 10:42:29 +00:00
Calum Lind
4b6ac1f4c4
[WebUI] Fix setting base path
Simplify the code for setting the base path, both via headers and
config. Replaced putchild since it is not recommended for dynamic
paths and overriding getChildWithDefault provides a proper solution.

This also fixes recursive base path problem with previous code where
appending base paths to URL would still return Deluge web e.g.

    http://localhost:8112/deluge/deluge/deluge

Removed getChild override by consolidating empty path conditional to
getChildWithDefault. This simplifies and combines the returning of
TopLevel resource for root path or base path.

Added workaround for test logfile error with forwardslash in filename
2023-03-03 09:37:46 +00:00
Calum Lind
683a4f906e
[WebUI] Minor refactoring
Simplify getting port for WebUI tests
Extract adding slashes to base to function
Use super function to simplify parent class calls
2023-03-03 09:37:36 +00:00
Calum Lind
e70a983a55
[Tests] Fix save_resume_data errors in test_torrent
Error in test_rename_unicode:

   TypeError: object MagicMock can't be used in 'await' expression

Fixed by using AsyncMock that can be awaited.
Added backport asyncmock for Python 3.7
2023-02-28 15:05:52 +00:00
Calum Lind
9ce8afe507
[Tests] Fix component warning in Plugin tests
The component fixture was warning about existing registered components since
the standalone client was setup before the componentregistry test was performed.
The problem is due to fixture ordering with the setup fixture running
first since it was marked with autouse followed by component fixture.

* Fixed warning by moving component fixture as a dependency of the set_up fixture
* Cleaned up unneeded code
* Added try..except to catch CorePluginBase KeyError deregistering
RPCServer since component fixture already removed it.
* Ignore test-specific Twisted readBody warnings
2023-02-28 14:21:18 +00:00
Calum Lind
f67fb4d520
[Tests] Remove unneeded component teardown
The component fixture calls shutdown so teardown not needed here
2023-02-28 14:20:09 +00:00
Calum Lind
d00068423f
[Tests] Ignore 3rd-party deprecation warnings in pytest
Move pytest config from tox to pyproject and ignore deprecation warning
generated in 3rd-party libraries.

Fixed GObject deprecation warning
2023-02-27 20:15:12 +00:00
DjLegolas
7336877928
[console] Fix host deletion
The host id didn't receive correctly and the indexing was not being
updated correctly.

Closes: https://github.com/deluge-torrent/deluge/pull/393
2023-02-27 17:39:00 +00:00
DjLegolas
543fce4f29
[console] Fix add host in connection manager
The input is being passed as `str` instead of `int`, so added a
conversion only if the string is indeed a decimal number.
In addition, closing the add host popup after adding it.

Closes: https://dev.deluge-torrent.org/ticket/3538
2023-02-27 17:39:00 +00:00
DjLegolas
38feea0fa4
[hostlist] Add port value validation
When in console, and adding a new host with an invalid port number, the
console starts printing many exceptions regarding the value.
Therefor, we will make sure that the port value is between 0 and 65535.
2023-02-27 17:39:00 +00:00
Calum Lind
7af584d649
[Console] Refactor Eventlog for readability 2023-02-27 17:39:00 +00:00
Calum Lind
1ba7beb7bc
[Console] Move migration func out of main 2023-02-27 17:38:59 +00:00
Calum Lind
f4f4accd34
[Console] Move eventlog class to separate file 2023-02-27 17:38:59 +00:00
Calum Lind
ae22a52f2f
[Console] Refactor callbacks and cleanup main 2023-02-27 17:38:59 +00:00
Calum Lind
22f74b60ce
[Console] Cleanup terminal resize handler
Improve readability

Move imports available on Windows out of try..except.

For future reference in Python 3.11 termios now has window size methods
but the added complexity of handling older Python versions is not worth
it.

https://docs.python.org/3/library/termios.html#termios.tcgetwinsize
2023-02-27 17:38:58 +00:00
Calum Lind
253eb2240b
[Console] Refactor main to use async instead of callbacks
Use the new maybe_coroutine decorator to replace callbacks with inline
async/await for cleaner code.
2023-02-27 17:38:58 +00:00
DjLegolas
6c924e6128
[GTK] Fix Add-torrent-dialog not respecting dirs
When adding a new torrent, there is a problem changing the path on
Windows machines, due to the difference between the way the files are
being read from the torrent and how we handle them in addtorrentdialog.
This effects both changing and showing the files in the UI.

Now, all path seperator is being considered and converted to slash '/'.

Closes: https://dev.deluge-torrent.org/ticket/3541
Closes: https://github.com/deluge-torrent/deluge/pull/395
2023-02-24 15:17:38 +00:00
Calum Lind
930cf87103
[Lint] Update pre-commit apps to latest versions
Also update github CI action versions
2023-02-24 14:59:15 +00:00
Calum Lind
45c9f3b90a
[Lint] Fix pre-commit isort install error
Update to latest version of isort to fix install error:

    RuntimeError: The Poetry configuration is invalid:
        - [extras.pipfile_deprecated_finder.2] 'pip-shims<=0.3.4' does not match '^[a-zA-Z-_.0-9]+$'

Also update the Linting CI to latest version
2023-02-24 14:58:26 +00:00
Torbjörn Lönnemark
13f81efe98
Update metainfo install path
The metainfo file was being installed into /usr/share/appdata, but that
path has been deprecated. Metainfo files should instead be installed
into /usr/share/metainfo.

Closes: https://dev.deluge-torrent.org/ticket/3394
Ref: https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#sect-Metadata-GenericComponent
Closes: https://github.com/deluge-torrent/deluge/pull/389
2023-02-24 13:42:19 +00:00
DjLegolas
98c5830013
[Packaging][Windows] Add DisplayVersion registry key
This addition will help tools (e.g. `winget`) to identify the version
of deluge

Closes: https://dev.deluge-torrent.org/ticket/3560
Closes: https://github.com/deluge-torrent/deluge/pull/402
2023-02-24 13:16:01 +00:00
DjLegolas
8332d1aa39
[Label] fix torrent deletion not removes from config
when deleting a torrent, the on deletion hook deletes the torrent from
the local config but does not save the new config to disk.
note that when setting a new label to a torrent, the config does save.

Closes: https://dev.deluge-torrent.org/ticket/3545
Closes: https://github.com/deluge-torrent/deluge/pull/397
2023-02-24 11:38:28 +00:00
JohnTheCoolingFan
6f7445be18
[Lable] Fix label display name in submenu
Previously every '_' in label's name was incorrectly replaced by '__'

Closes: https://github.com/deluge-torrent/deluge/pull/410
2023-02-24 11:28:41 +00:00
Tydus
fb30478123
[GTK3UI] Fix too low upper limit of upload/download in add torrent dialog
Closes: https://github.com/deluge-torrent/deluge/pull/398
2023-02-24 11:19:44 +00:00
DjLegolas
5d7b416373
[Core] Stop using libtorrent.add_torrent_params_flags_t
The `libtorrent.add_torrent_params_flags_t` is deprecated and when
`libtorrent` is being compiled without deprecated functionality, we will
fail on `AttributeError`.

Refs: 4947602a2f
Closes: https://dev.deluge-torrent.org/ticket/3581
Closes: https://github.com/deluge-torrent/deluge/pull/407
2023-02-24 10:44:57 +00:00
DjLegolas
4de754328f
[ConsoleUI] remove deferred being returned after command
A `return` statement was added in ece31cf for unit testing to work but
this resulted in Deferred printed in console output.

Added a test_start entry point to return the required deferreds while
removing the return from original start entrypoint.

Closes: https://dev.deluge-torrent.org/ticket/3582
Closes: https://github.com/deluge-torrent/deluge/pull/408
2023-02-24 10:43:09 +00:00
DjLegolas
c4b9cc7292
[tox] update tox.ini for support for tox 4
`tox` 4 now demands that `passenv` parameter will be comma-separated and
not space-seperated.

Closes: https://github.com/deluge-torrent/deluge/pull/409
2023-02-23 17:10:40 +00:00
Ryan Ernst
fa750c9fd0
[AutoAdd] Fixes #3515 - check for more torrent decode errors
https://github.com/deluge-torrent/deluge/pull/381 improved the situation
with possible errors during torrent decoding. However, the log message in
https://dev.deluge-torrent.org/ticket/3515 indicates a RuntimeError:

```
Traceback: <class 'RuntimeError'>: unexpected end of file in bencoded string
```

This commit adds RuntimeError to those caught while loading the torrent
to add.

Closes: https://github.com/deluge-torrent/deluge/pull/411
2023-02-23 17:08:48 +00:00
Calum Lind
2a945de069
[CI] Fix tests stalling/timing out
GitHub pytest runner stalling with the following error:

    [Errno 2] No such file or directory: '/opt/hostedtoolcache/Python/3.10.8/x64/lib/python3.10/site-packages/deluge/plugins'

This is related to the editable install of Deluge via pip

    pip install -e .

and the custom resource_filename in deluge.common is the source of the
problem where a DistInfoDistribution returns a different path to
EggInfoDistribution.

Working egg-info install

    >>> pkg_resources.get_distribution('Deluge')
    deluge 2.1.1.dev8 (/home/user/deluge)

    >>> type(pkg_resources.get_distribution('Deluge'))
    <class 'pkg_resources.EggInfoDistribution'>

    >>> pkg_resources.resource_filename('deluge', 'plugins')
    '/home/user/deluge/deluge/plugins'

This can identified by the `deluge.egg-info` directory in source
directory.

Broken dist-info install

    >>> pkg_resources.get_distribution('Deluge')
    deluge 2.1.1.dev8 (/opt/hostedtoolcache/Python/3.10.8/x64/lib/python3.10/site-packages)

    >>> type(pkg_resources.get_distribution('Deluge'))
    <class 'pkg_resources.DistInfoDistribution'>

    >>> pkg_resources.resource_filename('deluge', 'plugins')
    '/home/user/deluge/deluge/plugins'

This can be worked around by setting an env var that enables legacy mode
but long-term need to replace the custom resource_filename and replace
usage of pkg_resources.

https://setuptools.pypa.io/en/latest/userguide/development_mode.html#legacy-behavior
https://setuptools.pypa.io/en/latest/pkg_resources.html
2022-12-01 22:39:10 +00:00
Calum Lind
d0acd3e06e
[CI] Fix installing enchant for github docs workflow
The enchant package was renamed for version 2 to enchant-2 and original
enchant package removed in Ubuntu 22.04 so docs workflow failed

Fixed by using latest package and specifying ubuntu version to avoid
unexpected failures in future.
2022-12-01 13:11:53 +00:00
DjLegolas
3565a9a817
[WebUI] Fix TypeError in DelugeWeb constructor
In `twisted 22.10`, a check new for passing the `path` variable as
`bytes` in the `putChild` method.
We were enforcing this on every other place but the `__init__` of
`DelugeWeb` itself.

Ref: https://github.com/twisted/twisted/pull/11718
Closes: https://dev.deluge-torrent.org/ticket/3566
2022-12-01 12:52:00 +00:00
Calum Lind
b3d1fd79a8
Update changelog from 2.1.1 release 2022-07-10 14:06:34 +01:00
Calum Lind
b64084d248
[Docs] Update changelog and install details 2022-07-08 09:04:57 +01:00
Calum Lind
e120536d87
Fix parsing magnet with tracker tiers
Magnets with trackers specified with tr.x param were not being unquoted
so unusable raw tracker string was being set.

Fixed by unquoting tracker and adding test

See-also: https://dev.deluge-torrent.org/ticket/2716
2022-07-08 08:34:29 +01:00
Calum Lind
f52cf760e4
Fix missing trackers adding magnets
The changes to remove deprecated lt methods didn't account for magnet
trackers so magnets are missing trackers when added.

Previously the addition of trackers was handled by libtorrent when a url
was passed in add_torrent_params. The url parameter is deprecated so
instead we need to add both the info_hash and trackers.

Trac: https://dev.deluge-torrent.org/ticket/3530
2022-07-05 08:03:33 +01:00
Calum Lind
94d790c159
[CI] Bump ifaddr to 0.2.0
With release of ifaddr 0.2.0 no longer need to pin to github commit to
resolve Windows decoding issues.
2022-06-30 21:47:06 +01:00
Calum Lind
f78506161d
[CI] Fix failing Windows Python 3.10 tests
A recent dependency change caused the tests running on GitHub Actions
under Python 3.10.5 on Windows to fail when starting pytest run:

    ...
    INTERNALERROR>   File "<frozen importlib._bootstrap>", line 123, in acquire
    INTERNALERROR> KeyError: xxxx

The cause seems to have been a newer version of chardet package released
recently.

* Fixed by pinning chardet to v4
* Also pin Windows version to 2019 to match packaging workflow

See-also: https://github.com/deluge-torrent/deluge/actions/runs/2578427588
Issue: https://github.com/chardet/chardet/issues/265
2022-06-29 15:07:23 +01:00
Calum Lind
592b05cd87
back to development 2022-06-28 22:11:29 +01:00
Calum Lind
6c8f9ce756
Release 2.1.0 2022-06-28 22:07:35 +01:00
Calum Lind
19dba297ef
[Docs] Fix ReadTheDocs theme rendering issue
Fix rendering issues in ReadTheDocs by specifying latest version of
sphinx-rtd-theme. Normally not an issue to install this latest
dependency from doc/requirement.txt but RTD installs in the env an older
version (<0.5) before running requirements file install
thus sphinx-rtd-theme is not upgraded unless a version is specified.

See-also: https://github.com/readthedocs/sphinx_rtd_theme/issues/1115
2022-06-28 19:45:21 +01:00
N9199
cbacaf0545
[Core] Fix typo in AUTH_LEVEL_MAPPING
Closes: https://github.com/deluge-torrent/deluge/pull/387
2022-06-19 19:02:11 +01:00
Calum Lind
75db47fc1f
Update Changelog entries 2022-06-19 08:30:54 +01:00
Calum Lind
f1ec68704d
[CD] Cleanup pip cache in Win builds
- Use the more reliable setup-python cache
- Move the pip install to GTK install step for consistency
- Don't update pip to ensure consistent version
2022-06-13 19:37:41 +01:00
Martin Hertz
ae3fbcca77
[Packaging] Pinned Pyinstaller to v4.10 and readme update
Pin Pyinstaller to latest v4.x until issue of aborting upon missing typelibs for various unbuilt gst-modules can be properly investigated and resolved. Specific error for one of the modules being:

`36738 INFO: Loading module hook 'hook-gi.repository.Gst.py' from 'C:\\hostedtoolcache\\windows\\Python\\3.9.13\\x64\\lib\\site-packages\\PyInstaller\\hooks'...
Traceback (most recent call last):
  File "<string>", line 7, in <module>
gi.repository.GLib.GError: g-irepository-error-quark: Typelib file for namespace 'Gst', version '1.0' not found (0)
36870 ERROR: gi repository 'GIRepository 2.0' not found. Please make sure corresponding package is installed.
Traceback (most recent call last):
  File "<string>", line 4, in <module>
  File "C:\hostedtoolcache\windows\Python\3.9.13\x64\lib\site-packages\gi\__init__.py", line 139, in require_version
    raise ValueError('Namespace %s not available' % namespace)
ValueError: Namespace Gst not available`

Added `--no-index` to ensure pip doesn't install from Pypi

Closes: https://github.com/deluge-torrent/deluge/pull/386
2022-06-13 19:31:54 +01:00
Calum Lind
6a10e8f3cd
[Packaging] Bump Win dependencies
* Update Twisted and libtorrent to latest releases
* Update to v3 github actions that now use node 16
2022-06-12 20:05:04 +01:00
ibizaman
b0dba97fec
[Web] Accept charset in content-type for json messages
Trac: https://dev.deluge-torrent.org/ticket/3521
Closes: https://github.com/deluge-torrent/deluge/pull/385
2022-06-12 16:08:36 +01:00
Martin Hertz
d7c520c85e
[WebUI] Fixed 'Complete Seen' and 'Completed' sorting
Closes: https://github.com/deluge-torrent/deluge/pull/384
2022-05-17 22:45:37 +01:00
Martin Hertz
ee3180fd94
[Notifications] Fix UnicodeEncodeError upon non-ascii torrent name
smtplib.SMTP.sendmail expects 'msg' in string of ascii chars or bytes,
where the former gets encoded to bytes through ascii codec, hence
raising said error, but now fixed by encoding to bytes ourself through
utf-8

Closes: https://github.com/deluge-torrent/deluge/pull/383
2022-05-17 22:42:05 +01:00
Chase Sterling
47e548fdb5
[Core] Refactor prefetch_metadata for more clarity
Just trying to clean up some of the more complicated callback logic.

Notable changes:

* The test was awaiting a DeferredList. By default that will eat
exceptions and just add them to the result list (including test
assertion exceptions.) Added fireOnOneErrback=True to make sure that
wasn't happening.  * Moved the logic for multiple calls to await the
same response into torrentmanager from core, so no matter where the
prefetch is called from it will wait for the original call.
* Implemented the multiple calls with an explicit queue of waiting
callbacks, rather than a callback callback chain.  * Moved to one inline
async function rather than split into a main and callback after alert
function.
* Added some more type hints to the stuff I changed.

Adjusted test since we are using prefetch as an async function now
we have to schedule the alert to come after we start awaiting the
prefetch call.

Closes: https://github.com/deluge-torrent/deluge/pull/368
2022-05-17 22:15:55 +01:00
Calum Lind
cd63efd935
[Console] Fix curses.init_pair raise ValueError on Py3.10
Fix ValueError crash for console users with Python 3.10

Trac: https://dev.deluge-torrent.org/ticket/3518
See-also: https://docs.python.org/3/whatsnew/3.10.html#curses
2022-05-01 21:09:41 +01:00
Calum Lind
7f0a380576
[Core] Cleanup temp files in add_torrent_url
Temporary torrent files are not deleted by add_torrent_url. Not as big a
problem as with tracker icons pages but should be removed after use.

Fixed by updating the method to use async and a try..finally cleanup
block.

Perhaps could be refactored to not require temporary files and instead
store the downloaded torrent as object for passing to add_torrent_file.

Trac: https://dev.deluge-torrent.org/ticket/3167
2022-05-01 20:38:09 +01:00
Calum Lind
68c75ccc05
[TrackerIcons] Cleanup tmp files created by downloading page
Temporary files created while download host html page are no cleaned up
if the download fails.

Fixed by adding a 'finally' step in the callback chain to delete any
created temporary files.

Added tests to ensure the temporary files are deleted, using a fixture
that creates a known filename for the test.

Trac: https://dev.deluge-torrent.org/ticket/3167
2022-05-01 20:35:28 +01:00
Calum Lind
96a0825add
[TrackerIcon] Use httpdownloader page redirect handling
User reported infinite redirecting when attempting to fetch tracker
icon.

Fixed by allowing httpdownloader and RedirectAgent to handle page
redirects including catching infinite redirects

Trac: https://dev.deluge-torrent.org/ticket/3167
See-also: https://github.com/twisted/twisted/blob/5c24e9/src/twisted/web/client.py#L168
2022-05-01 20:35:28 +01:00
Calum Lind
61a83bbd20
[Tests] Remove winreg interface name check
GitHub CI tests on Windows failing for get_windows_interface_name so
remove the fragile tests since not a requirement to be this specific
with testing whether name exists for these methods relying on standard
lib or 3rd-party libs.
2022-05-01 20:32:57 +01:00
deaddrop9
bc6611fc0d
[AutoAdd] Verify torrent decode and errors cleanly if invalid
Watch folder was disabled in AutoAdd and torrent filename is unchanged
if an invalid torrent was added.

Trac: https://dev.deluge-torrent.org/ticket/3515
Closes: https://github.com/deluge-torrent/deluge/pull/381
2022-05-01 18:34:19 +01:00
DjLegolas
970a0ae240
[lint] update black package
Previous version (22.1.0) didn't support `click` version 8.1.0.
Updating to 22.3.0 to resolve.

See: https://github.com/psf/black/issues/2964
Closes: https://github.com/deluge-torrent/deluge/pull/382
2022-05-01 18:31:12 +01:00
Calum Lind
5acb57b5af
[CI] Use setup-python action pip cache
Replace custom pip cache that didn't work correctly on Windows with
option to use pip cache in setup-python action
2022-03-02 13:08:02 +00:00
DjLegolas
7fa0af3446
[Core] Fixed KeyError in sessionproxy after torrent delete
When several torrents are being removed from session, an exception was
being raised in a callback function `on_status` with the `torrent_id` of
one (or more) of the removed torrents.
Now we will catch when the torrent does not exist anymore and print a
debug log about it.

Closes: https://dev.deluge-torrent.org/ticket/3498
Closes: https://github.com/deluge-torrent/deluge/pull/341
2022-03-02 13:00:45 +00:00
DjLegolas
a954348567
[CI] Use libtorrent pypi install
Up until now, the linux installation source of libtorrent was launchpad,
because there was no other source, and we wanted a debug version of lt.

With pypi wheel versions now available use lt in the requirements.txt
file.

Closes: https://github.com/deluge-torrent/deluge/pull/364
2022-03-02 12:55:19 +00:00
DjLegolas
13be64d355
[CI] Changed tested python version to 3.7 and 3.10
We cannot add python 3.6 because there is no precompiled version of it to used.
Therefor, will be using 3.7 as the minimum version in CI.
In addition, dropped version limits from pytest.
2022-03-02 12:53:29 +00:00
Calum Lind
11fe22e4cd
[Tests] Remove reference to Twisted Trial
With the move to pytest remove remainings documentation or comments that
refer to Trial.
2022-03-02 12:45:15 +00:00
Calum Lind
a683b7e830
[Tests] Skip SNI icon test
The SNI icon test is failing due to seo.com removing their favicon.

A new test to replace it would be needed to check SNI support. Ideally
new tests would not rely on external sites.
2022-03-02 12:18:05 +00:00
marik
b0f80f9654
[Console] Swap j and k key's behavior to fit vim mode
There is a problem in the Deluge's Console UI. This UI supports the j
and k keys as up and down, but for some reason they are inverted. This
commit inverts back the behaviour of j and k in several places.

Resolves: https://dev.deluge-torrent.org/ticket/3483
Closes: https://github.com/deluge-torrent/deluge/pull/377
2022-03-02 11:38:36 +00:00
Calum Lind
f9ca3932a8
[GTK] Remove unneeded glade icon activatable False property
This propery cause issue with added extra space in the entries, likely a
minor GTK bug and property being leftover from GTK2 migration. The
default is True and should have no effect since no icon is shown.
2022-03-02 11:29:17 +00:00
Calum Lind
5ec5271fdd
[GTK] Refactor Connection Manager Add Host dialog
Replace table with grid and use single column for entries.
2022-03-02 11:09:30 +00:00
Calum Lind
e15731fcd4
[GTK] Fix obscured port number in Connection Manager Add Host
A visual problem with the port spin button meant that the port number
was not fully visible in the entry field.

The problem is related to icon activatable property value set to False
when default is True. Although no icon is used is creates a left-hand
5px space in the entry which narrows the available text space and the
entry does not expand to account for this.

Fixed by removing primary_icon_activatable and
secondary_icon_activatable properies so that default values of True is
used.
2022-03-02 11:02:26 +00:00
Hugo Osvaldo Barrera
2962f7cd2c
[Packaging] Add systemd user services
Files should be installed into /usr/lib/systemd/user/

Unlike the existing service file, this one configures deluge to run as a
desktop session user. The difference between the services files is the
use of multi-user.target in system service which does not exist for user
services so requires default.target.

Including the Slice indicates to the service manager that this is a
background service. This can be used to handle OOM situations, or
prioritising foreground processes. There's no equivalent for system
services.

Refs: https://dev.deluge-torrent.org/ticket/2034
Closes: https://github.com/deluge-torrent/deluge/pull/380
2022-03-02 09:26:23 +00:00
tbkizle
c89a366dfb
[Hostlist] Support IPv6 in host lists
socket.gethostbyname does not support IPv6 name resolution, and
getaddrinfo() should be used instead for IPv4/v6 dual stack support.

Closes: https://github.com/deluge-torrent/deluge/pull/376
2022-02-18 23:05:12 +00:00
Calum Lind
5f8acabb81
[Console] Fix torrent details status error
The torrent status num_peers and num_seeds was replaced for session
status keys by accident as part of replacing deprecated session keys
so revert those changes

Ref: 2bd095e5bf
2022-02-17 20:30:05 +00:00
Calum Lind
055a84bb15
[Core] Fix Twisted fromCoroutine AttrError
Users with older versions of Twisted <= 21.2 were encoutering the
following error:

    File "/home/calum/projects/deluge/deluge/decorators.py", line 191, in activate
      d = defer.Deferred.fromCoroutine(self.coro)

    builtins.AttributeError: type object 'Deferred' has no attribute 'fromCoroutine'

Fixed by falling back to ensureDeferred since fromCoroutine was
introduced in Twisted 21.2 as a saner name for handling of coroutines.

Ref: https://twistedmatrix.com/trac/ticket/9825
2022-02-16 16:20:54 +00:00
Calum Lind
03938839e0
[Docs] Add permanent discord invite link
Default discord invites expire after 7 days so replace with non-expiring
invite
2022-02-15 15:30:08 +00:00
Chase Sterling
8ff4683780
Automatically refresh and expire the torrent status cache.
Stop at ratio was not working when no clients were connected, because
it was using a cached version of the torrent status, and never calling
for a refresh. When a client connected, it called for the refresh and
started working properly.

Closes: https://dev.deluge-torrent.org/ticket/3497
Closes: https://github.com/deluge-torrent/deluge/pull/369
2022-02-15 15:14:40 +00:00
Calum Lind
62a4052178
[Docs] Remove custom mock to fix autodoc typing errors
If a libtorrent return type was specified e.g.

   def get_lt_status(self) -> 'lt.torrent_status'

Even as a string autodoc_typehints module would raise and error:

    Handler <function process_docstring at 0x7f6c16c8ec10> for event 'autodoc-process-docstring' threw an exception (exception: getattr(): attribute name must be string)

This was a result of using a custom mock in Sphinx autodoc config and
this Mock object name or qualname returns an object instead of str.

Testing with putting modules in autodoc_mock_imports again showed no
issues so removing custom mock

Ref: https://github.com/tox-dev/sphinx-autodoc-typehints/issues/220
2022-02-15 11:49:54 +00:00
Calum Lind
8ece036770
[WebUI] Move HTML entity encoding to client
We should not be mangling the torrent data in the JSON API since this
can have unintended consquences with names and filepaths that can be
edited. If we escape those symbols in the JSON API then the data no
longer matches that stored by core. Therefore shift the encoding to the
client and consider dealing separetely with these entities when the user
first adds a torrent.

* Created a modified htmlEncode in Deluge Formatter based on extjs
method that also encodes single quotes.
* Removed renderers in ListViews since only templates specified via tpl
are used and any render attribute specified was a no-op.
* Removed old buggy escapeHtml

Resolves: https://dev.deluge-torrent.org/ticket/3459
Ref: https://docs.sencha.com/extjs/6.5.3/modern/src/String.js.html#Ext.String-method-htmlEncode
Ref: https://docs.sencha.com/extjs/3.4.0/source/Format.html#Ext-util-Format-method-htmlEncode
2022-02-14 18:44:19 +00:00
Calum Lind
a5503c0c60
[WebUI] Fix encoding HTML entities for torrent attributes
Ensure all torrent attributes that might contain malicious HTML entities
are encoded.

By allowing HTML entities to be rendered it enable malicious torrent
files to perform XSS attacks.

Resolves: https://dev.deluge-torrent.org/ticket/3459
2022-02-14 18:43:20 +00:00
Calum Lind
f754882498
[GTK] Increase connection mgr default height
Could not see more than one host when connection manager opens so need to
scroll or resize window

Increased default height to now show three hosts when first opens

Closes: https://dev.deluge-torrent.org/ticket/3431
2022-02-13 14:45:29 +00:00
Henry Kwan
191549074c
[GTK] Fix ui logic/bug of checked move_completed
if move_completed is checked/True, options should be updated, not the
other way round

The path was updated the first time the move_completed option is selected
and then ignored on further updated to the path.

Fixed by checking instead if the path has changed.

Closes: https://github.com/deluge-torrent/deluge/pull/374
2022-02-13 13:55:54 +00:00
Calum Lind
2ec6e10c8e
[Lint] Update linter version and fix issues
Notable changes:

* Prettier >=2.3 with more consistent js assignments
* Black now formats docstrings
* Added isort to list of autoformaters
* Update flake8 config for v4

Ref: https://prettier.io/blog/2021/05/09/2.3.0.html
2022-02-13 13:38:27 +00:00
DjLegolas
2bd095e5bf
[Core] Stopped using libtorrent deprecated functions
As part of the process of adding support to LT 2.0, we should stop using
all deprecated function, as some (if not all) were removed.
For this process, we should use the LT 1.2 upgrade (guide)[1].

The change includes:
* stop using file entries directly
* start using the torrent handle's set/unset flags
* stop using url key in add_torrent_params (for magnet)
* stop accessing resume_data from save_resume_data_alert
* stop using deprecated session status keys in UI

[1] https://libtorrent.org/upgrade_to_1.2-ref.html
Closes: https://dev.deluge-torrent.org/ticket/3499
Closes: https://github.com/deluge-torrent/deluge/pull/342
2022-02-13 11:36:04 +00:00
DjLegolas
513d5f06e5
[lt] Upgraded libtorrent minimum version to 1.2
As part of the preparations for libtorrent 2.0, we should stop supporting
lower versions of it.
2022-02-13 11:32:30 +00:00
Chase Sterling
a1da2058bc
[Core] Enable file_completed_alert handling.
Without adding file_progress to the alert mask, the
TorrentFileCompletedEvent would never fire.

Closes: https://dev.deluge-torrent.org/ticket/3421
Closes: https://github.com/deluge-torrent/deluge/pull/370
Ref: https://libtorrent.org/upgrade_to_1.2-ref.html
2022-02-13 11:25:45 +00:00
Calum Lind
af26fdfb37
[GTK] Fix adding daemon accounts
Errors were raised when trying to add a new daemon account due to dialog
being destroyed before looking up widget values.

Fixed by saving widget values before destroying.

Refactored code to be simplified with a named tuple for the account
details instead of separate attributes and modernized the preferences
dialog creation and account saving by replacing callback functions.
2022-02-12 23:40:00 +00:00
bendikro
66b5a2fc40
[Console] Fix incorrect test for when a host is online
The tests in connectionmanager for when a host is online are broken
and always considers a host as online.

When an error occurs in e.g. in _on_connect_fail, report_message()
in PopupsHandler expects the message to be string, not a Twisted Failure.
Fix by checking if message is string and log a warning before converting
to string.

Closes: https://github.com/deluge-torrent/deluge/pull/277
2022-02-12 19:03:52 +00:00
DjLegolas
29f0789223
[plugins] Add dev links script for Windows
Currently, when creating a new plugin, a script for creating
the dev links was created for *NIX systems only.
Now, it will detect the system type and create the correct
script:
Windows: create_dev_links.bat
*NIX: create_dev_links.sh

Closes: https://github.com/deluge-torrent/deluge/pull/257
2022-02-12 17:59:37 +00:00
DjLegolas
f8f997a6eb
[Config] Fix callLater func missing args
In a6840296, a refactor to the `config` class was introduced.
The change included an internal wrapper for `reactor.callLater`, for lazy
import, but didn't wrap it correctly and therefor, no args/kwargs were
passed to the wrapped method.
Furthermore, the exception was silently ignored.
This caused changes to be ignored and not applied, including
`preferencesmanager._on_config_value_change` callback.

Closes: https://github.com/deluge-torrent/deluge/pull/372
2022-02-12 17:14:19 +00:00
Chase Sterling
374997a8d7
[Tests] Make file priority test more consistent.
Our file priority test was using time.sleep to wait until libtorrent
had processed the command. This was sometimes not long enough and the
test would fail. On libtorrent 2.0.3+ there is an alert when the
process has finished, switch to waiting for that in this test to make
the test more consistent. On older libtorrent, make the delay a bit
longer, to try to make the test more consistent there as well.

Closes: https://github.com/deluge-torrent/deluge/pull/373
2022-02-12 17:12:05 +00:00
Chase Sterling
dabb505376
[Core] Document all exported core methods with type hints
Standardize docstrings in core.py to google standard.
Remove type hints in docstrings in favor of the ones in method signatures.

Use function signature type hints in autodoc generated docs.

Change Deferred type hints to strings.

Older versions of twisted (<21.7) don't
support generic Deferred type hinting,
this prevents crashes on those versions.

Closes: https://github.com/deluge-torrent/deluge/pull/359
2022-02-11 08:48:58 +00:00
Chase Sterling
aa74261d50
[GtkUI] Fix ETA being copied to neighboring empty cells
An optimization that avoided re-rendering treeview cells sometimes
went wrong, and rendered a value from the wrong row when moving
the mouse around the torrentview window.

Closes: https://dev.deluge-torrent.org/ticket/3500
Closes: https://github.com/deluge-torrent/deluge/pull/371
2022-02-11 08:31:03 +00:00
Calum Lind
b29829f571
[CI] Fix package build error
Not all dependencies were installed due to adding a comment in the
middle of the pip install command

Also need to specify Twisted extras to match requirement.txt
2022-02-09 20:39:48 +00:00
Calum Lind
d559f67ab9
[Packaging] Fix pyinstaller to find installed deluge package data
Instead of relying on the source code paths use the pip installed Deluge
package data.
2022-02-09 19:53:17 +00:00
Calum Lind
d4f8775f44
[CI] Use working dir to shorten commands
Making the workflows more readable
2022-02-09 19:53:17 +00:00
Calum Lind
50647ab3a5
[CI] Replace custom twisted package with pre-release
Fix for Windows simulate error has been merged and in 22.2.0rc

Ref: https://github.com/twisted/twisted/pull/1679
2022-02-09 19:53:17 +00:00
Calum Lind
90744dc2e6
[GTK] Workaround crash on Windows with ico or gif icons
A problem with GdkPixbuf loaders on Windows causes a hard crash when
attempting to load ico or gif tracker icons.

Added a workaround by skipping these icon types until a more permanent
solution is found.

Ref: https://dev.deluge-torrent.org/ticket/3501
2022-02-09 19:53:17 +00:00
Calum Lind
24a3987c3a
[GTK] Refactor out get_pixbuf_at_size
The functionality of get_pixbuf and get_pixbuf_at_size is almost
identical so reuse get_pixbuf with an optional size arg.
2022-02-09 19:53:15 +00:00
Calum Lind
e87236514d
[Build] Fix entry point build errors
Fixed a mistake settings entry points in setup.py. Replaced with simpler
logic since gui_scripts only affect Windows.

Fixed entry point changes affecting pyinstaller build

Corrected deluge-web.exe to have no console instead of
deluge-web-debug.exe
2022-02-06 21:02:43 +00:00
Chase Sterling
2fb41341c9
[Core] Convert inlineCallbacks to maybe_coroutine
Make logging functions synchronous.

They were not calling any async functions, and wrapping them in
maybe_coroutine caused reactor to be imported before gtkui could
install the gtk reactor.

Closes: https://github.com/deluge-torrent/deluge/pull/353
2022-02-06 16:45:37 +00:00
Chase Sterling
b76f2c0f20
[Decorators] Add maybe_coroutine decorator
- Clean up callback hell by making more code inline
- Use async/await syntax as it has more modern niceties than inlineCallbacks
  - Also gets us closer if we want to transition to asyncio in the future
  - `await` is usable in places that `yield` is not. e.g. `return await thething` `func(await thething, 'otherparam')`
  - IDEs know async semantics of async/await syntax to help more than with `inlineCallbacks`
- `maybe_coroutine` decorator has nice property (over `ensureDeferred`) that when used in a chain of other coroutines, they won't be wrapped in deferreds on each level, and so traceback will show each `await` call leading to the error.
- All async functions wrapped in `maybe_coroutine` are 100% backwards compatible with a regular Deferred returning function. Whether called from a coroutine or not.
- Use Deferred type hints as strings since older versions of twisted
(<21.7) don't support generic Deferred type hinting.
2022-02-06 16:42:13 +00:00
Calum Lind
bd88f78af6
[Plugins] Fix namespace deprecation warning
The plugin namespace was changed from deluge.plugins to deluge_
in 535b13b5f1 but deprecation warning was not updated.
2022-02-06 16:27:47 +00:00
Calum Lind
bf97bec994
[Config] Add mask_funcs to help mask passwords in logs
Added a new Config class parameter `log_mask_funcs` to enable config
instances hide sensitive information that would normally appear in
config debug logs.

Added mask password function to hostlist to replace passwords with '*'s
in logs.

Closes: https://github.com/deluge-torrent/deluge/pull/363
2022-02-06 16:15:34 +00:00
Calum Lind
a27a77f8c1
[Windows] Use gui_scripts for web and daemon entry points
Hide the console cmd popup when using deluge-web.exe ordeluged.exe on
Windows.

By using gui_scripts it will disable stdin and stdout for these
executable but there are `-debug` versions available if that is
required.

Ref: https://packaging.python.org/en/latest/specifications/entry-points/#use-for-scripts
2022-02-06 15:15:25 +00:00
kingamajick
e8fd07e5e3
[Console] Add the torrent label to info command
Closes: https://dev.deluge-torrent.org/ticket/1556
Closes: https://github.com/deluge-torrent/deluge/pull/356
2022-02-06 15:11:29 +00:00
Calum Lind
1089adb844
Revert "[Core] Document all exported core methods with type hints"
Typing is broken with older versions of Twisted e.g. with v21.2

    deluge/deluge/core/core.py", line 404, in Core
        ) -> defer.Deferred[str]:
    TypeError: 'type' object is not subscriptable

Also it might not be compatible with Python 3.6 or 3.7 with use of
certain types such as dict rather than Dict

This reverts commit 4096cdfdfe.

Ref: https://twistedmatrix.com/trac/ticket/9816
2022-02-05 17:50:54 +00:00
Chase Sterling
4096cdfdfe
[Core] Document all exported core methods with type hints
Standardize docstrings in core.py to google standard.
Remove type hints in docstrings in favor of the ones in method signatures.

Use function signature type hints in autodoc generated docs.

Closes: https://github.com/deluge-torrent/deluge/pull/359
2022-02-05 17:23:50 +00:00
Calum Lind
099077fe20
[Config] Replace custom property decorator 2022-02-05 16:13:10 +00:00
Calum Lind
a684029602
[Config] Refactor config class
* Refactored duplication with setting config key and logging
* Simplified lazy importing reactor for callLater. This lazy importing
is required for testing and also prevents Gtk UI lockup if reactor
imported in Config.
* Fixed saving config to file when setting a key that doesn't exist yet.
This was due to returning early in the set_item method.
* Added a `default` arg to set_item to prevent saving to file when only
setting a default value for a key in init.
* Moved casting value to existing key type from set_item to dedicated
function.
2022-02-05 16:01:22 +00:00
doadin
8b0c8392b6
[Log] Fix crash logging to Windows protected folder
Have the log dir be a protected windows folder and Deluge crashes.
Windows blocks access to the dir and so it fails. It will fail trying to write
to any protected folder. Should probably just pass on the error maybe
and maybe log to stdout and log a message saying access was blocked or
something.

    .\deluge-debug -L debug -l E:\Documents\deluge.log
    ...
    Failed to execute script 'deluge-debug-script' due to unhandled exception!

Closes: https://dev.deluge-torrent.org/ticket/3502
Closes: https://github.com/deluge-torrent/deluge/pull/358
2022-02-03 22:46:33 +00:00
Chase Sterling
222aeed2f3
Remove legacy PY2 sys.argv unicode handling
Fixed crash when sys.stdout was None

When using pythonw on windows, sys.stdout and stdin are None. We had a
legacy unicode_argv handler that was crashing in this instance. Just
removed that, since sys.argv is always unicode on python 3 to fix the
crash.

Closes: https://github.com/deluge-torrent/deluge/pull/361
2022-02-03 22:38:37 +00:00
Chase Sterling
ece31cf3cf
[Tests] Transition tests to pure pytest
Convert all the twisted.trial tests to pytest_twisted. Also move off of unittest.TestCase as well. Seems there were several tests which weren't actually testing what they should, and also some code that wasn't doing what the broken test said it should.

Goals:

    Remove twisted.trial tests
    Move to pytest fixtures, rather than many classess and subclasses with setup and teardown functions
    Move away from self.assertX to assert style tests
    FIx broken tests

Going forward I think these should be the goals when adding/modifying tests:

* Don't use BaseTest or set_up tear_down methods any more. Fixtures should be used either in the test module/class, or make/improve the ones available in conftest.py
* For sure don't use unittest or twisted.trial, they mess up the pytest stuff.
* Prefer pytest_twisted.ensureDeferred with an async function over inlineCallbacks.
  - I think the async function syntax is nicer, and it helps catch silly mistakes, e.g. await None is invalid, but yield None isn't, so if some function returns an unexpected thing we try to await on, it will be caught earlier. (I struggled debugging a test for quite a while, then caught it immediately when switching to the new syntax)
  - Once the maybe_coroutine PR goes in, using the async syntax can also improve tracebacks when debugging tests.

Things that should probably be cleaned up going forward:

* Remove BaseTestCase
* Remove the subclasses like DaemonBase in favor of new fixtures.
  * I think there are some other utility subclasses that could be removed too
* Perhaps use parameterization in the ui_entry tests, rather that the weird combination of subclasses and the set_var fixture I mixed in.
* Convert some of the callback stuff to pytest_twisted.ensureDeferred tests, just for nicer readability

Details relating to pytest fixtures conftest.py in root dir:
 * https://github.com/pytest-dev/pytest/issues/5822#issuecomment-697331920
 * https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files

Closes: https://github.com/deluge-torrent/deluge/pull/354
2022-02-03 22:29:32 +00:00
Calum Lind
0fbb3882f2
[CI] Fix checkout action missing fetch depth
Need a fetch depth greater than 1 to find latest tag.
2022-02-01 06:45:10 +00:00
Calum Lind
73394f1fc5
[CI] Fix run manual packaging workflow for tag
To allow packaging any commit the workflow needs to separately checkout
the source code from the current code containing the packaging scripts.
2022-01-30 17:42:15 +00:00
Calum Lind
9b043cf2c1
[CI] Allow manual specifying tag in packaging workflow 2022-01-30 17:03:33 +00:00
DjLegolas
1cd005c272
[Gtk] Fixed edit torrents dialogs windows close issues
Up until now, when closing the Add or Edit dialogs, of the `Edit Torrents`, using the top-right X
button or using the Escape key, they were being destroyed without any way to reopening them, and
it was breaking the `Edit Torrents` window itself.
Now both dialogs have the right handlers for handing the closing process without breaking anything.

Closes: deluge-torrent/deluge#324
Closes: https://dev.deluge-torrent.org/ticket/2434
2022-01-30 16:26:44 +00:00
Facundo Acevedo
4107bf8f25
[Blocklist] Add frequency unit to interval label
Closes: https://dev.deluge-torrent.org/ticket/3492
Closes: https://github.com/deluge-torrent/deluge/pull/322
2022-01-30 16:21:39 +00:00
Chase Sterling
49bedda956
[Docs] Add discord link to readme
Closes: https://github.com/deluge-torrent/deluge/pull/350
2022-01-30 16:17:05 +00:00
tbkizle
540d557cb2
[Common] Add is_interface to validate network interfaces
Libtorrent now supports interface names instead of just IP address so
add new common functions to validate user input.

* Added is_interface that will verify if a libtorrent interface of name
or IP address.
* Added is_interface_name to verify that the name supplied is a valid
network interface name in the operating system.
  On Windows sock.if_nameindex() is only supported on 3.8+ and does not
return a uuid (required by libtorrent) so use ifaddr package. Using git
commit version for ifaddr due to adapter name decode bug in v0.1.7.
On other OSes attempt to use stdlib and fallback to ifaddr if installed
otherwiser return True.
* Added tests for is_interface & is_interface_name
* Updated UIs with change from address to interface
* Updated is_ipv6 and is_ipv4 to used inet_pton; now supported on
Windows.

Ref: https://github.com/pydron/ifaddr/pull/32
Closes: https://github.com/deluge-torrent/deluge/pull/338
2022-01-30 16:13:27 +00:00
Chase Sterling
d8acadb085
[Tests] fix/enable most ui tests on Windows
Closes: https://github.com/deluge-torrent/deluge/pull/348
2022-01-26 18:44:54 +00:00
Chase Sterling
932c3c123f
[Tests] fix torrentview tests (new default column was added) 2022-01-26 18:44:48 +00:00
Chase Sterling
986375fa86
[Tests] Make sure to return exit code still when ending test daemon
Use a separate Client instance to call test daemon shutdown
2022-01-26 18:44:48 +00:00
Chase Sterling
4497c9bbcc
[Tests] Escape backslashes in filename in webserver test 2022-01-26 18:44:47 +00:00
Chase Sterling
23f7c4dd6e
[Tests] Change example files in files tab test to sort the same on linux/windows 2022-01-26 18:44:47 +00:00
Chase Sterling
a41f950d09
[Tests] Enable more tests that now work on Windows 2022-01-26 18:44:47 +00:00
Chase Sterling
209716f7cd
[Tests] Shutdown test daemon cleanly using rpc. (needed for Windows)
Escape backslashes in config path for test daemon startup.
2022-01-26 18:44:47 +00:00
Chase Sterling
3dca30343f
[Tests] Make failure message more clear when test daemon doesn't shut down cleanly 2022-01-26 18:44:47 +00:00
Chase Sterling
71cde7c05e
[Tests] Enable unicode path test on Windows 2022-01-26 18:44:47 +00:00
Chase Sterling
dbf3495c4e
[Tests] Fix erroneous windows line endings in test state file 2022-01-26 18:44:47 +00:00
Chase Sterling
fffc6ab7d7
[Tests] Enable metafile test on Windows 2022-01-26 18:44:46 +00:00
Chase Sterling
a73e01f89f
[Tests] Fix maketorrent test on Windows 2022-01-26 18:44:46 +00:00
Chase Sterling
87ec04af16
Fix crash when logging errors initializing gettext 2022-01-26 18:44:46 +00:00
Chase Sterling
d8746a8852
[Core] Return plugin keys with get_torrents_status
When requesting all keys, get_torrents_status was missing plugin added keys
This commit brings the behavior in line with get_torrent_status, and deluge 1.3

Closes: https://dev.deluge-torrent.org/ticket/3357
Closes: https://github.com/deluge-torrent/deluge/pull/347
2022-01-26 18:40:16 +00:00
DjLegolas
7c9a542006
[GTK] Hide account password length in log
We should not let anyone know the account's password length,
as it can help to crack it.
Instead, we will print a constant amount (10) of asterisks.

Closes: https://github.com/deluge-torrent/deluge/pull/346
2022-01-23 16:39:24 +00:00
Cirno the Strongest
e75ef7e31f
[Packaging] Simplify PyInstaller spec file
This makes the process of editing the file much more pleasant and
removes duplicate code.

Fixed collecting twisted package which brings both build speed
improvements but also decreases package size, as it stops PyInstaller
from bundling tests (actually, some tests might even execute during
import and break build if they're designed to throw!) used by
PyInstaller

Closes: https://github.com/deluge-torrent/deluge/pull/342
2022-01-23 16:04:33 +00:00
DjLegolas
4f87612a0f
[Common] Replace distro.linux_distribution function
As of distro (1.6.0)[1], this function is marked as deprecated.
Instead, we will use the underlying functions directly, as explained in the (docs)[2].

[1] https://github.com/python-distro/distro/issues/263#issuecomment-927098357
[2] https://distro.readthedocs.io/en/latest/#distro.linux_distribution

Closes: https://github.com/deluge-torrent/deluge/pull/345
2022-01-22 12:06:38 +00:00
tbkizle
2cad0f46f2
[CI] Add pygame to windows package build/spec file
pygame is required by notification plugin for sounds
2022-01-21 13:12:23 +00:00
Calum Lind
5931d0cc0b
[CI] Fix package job not running with PR label
The job would only run when the PR was labeled since
`github.event.label.name` only available when `labeled` type event
recieved
2022-01-21 12:53:54 +00:00
Calum Lind
9d4ca77ef7
[CI] Cleanup packaging dependencies 2022-01-21 12:53:54 +00:00
tbkizle
ad27a278fd
[GTK UI]About Dialog Update year 2022-01-21 12:53:54 +00:00
Calum Lind
4f17fc41a5
[Packaging] Disable GTK CSD by default on Windows
CirnoT reported how they felt that GTK3 is not reliable on Windows.
Seeing some weird issues where clicking Deluge icon on taskbar does
bring window to front but doing so again does not minimize it as one
would expect. By using GTK_CSD=0 this would reduce these problems.

> If changed to 0, this disables the default use of client-side
decorations on GTK windows, thus making the window manager responsible
for drawing the decorations of windows that do not have a custom
titlebar widget.

This can be overridden by a global env var.

Ref: https://github.com/deluge-torrent/deluge/pull/331#issuecomment-1012311605
Ref: https://docs.gtk.org/gtk3/running.html
2022-01-21 12:53:54 +00:00
Calum Lind
15d2d27a53
[CI] Specify github windows server version
To ensure builds don't break avoid using windows-latest

Refs: https://github.com/actions/virtual-environments/issues/4856
2022-01-21 10:16:15 +00:00
Calum Lind
65e5010e7f
[Core] Add pygeoip dependency support
Provide support for the pure-python pygeoip as compiled GeoIP is not
always available.

Ref: https://dev.deluge-torrent.org/ticket/3271
2022-01-21 10:02:18 +00:00
DjLegolas
9b97c74025
[GTK] Added a torrent menu option for magnet copy
this will lined-up with the WebUI, which already have this option.
in addition, it will not open the Add Torrent URL dialog after copied,
which happens automatically when there is torrent/magnet URIs in the clipboard.

Closes: deluge-torrent/deluge#328
Closes: https://dev.deluge-torrent.org/ticket/3489
2022-01-21 09:41:59 +00:00
Calum Lind
d62362d6ae
[CI] Improve packaging workflow
Include arch in artifacts so they can be downloaded separately

Added libtorrent 2.0 to matrix since users often request latest
libtorrent.

Renamed workflow to make it's purpose clearer
2022-01-20 15:31:15 +00:00
Calum Lind
1a9affbbac
[Build] Add missing setuptools to requirements
Although likely to already be installed this is a runtime requirement
for Deluge
2022-01-20 14:49:53 +00:00
Calum Lind
2316088f5c
[CI] Remove PR specified branch
This branch name is the head name not the base name so prevents the job
running unless submitted has branch name that matches
2022-01-14 13:07:22 +00:00
Calum Lind
d14310078b
[CI] Fix typo in CD 2022-01-13 22:46:02 +00:00
Calum Lind
1696c69776
[CI] Fix windows build tag exclude
Fixes error:

    you may only define one of `tags` and `tags-ignore` for a single event
2022-01-13 22:36:56 +00:00
Calum Lind
5f96ea4217
[CI] Restrict creating Windows installer
Limit the running of this job by only running on develop, tags and pull
requests that have label 'windows'
2022-01-13 22:23:25 +00:00
tbkizle
491a20cb08
Fix Execute and Extractor Plugins
Include missing twisted requirements resulting in errors:

    ModuleNotFoundError: No module named 'twisted.internet.utils'
2022-01-13 22:23:25 +00:00
tbkizle
490fb898af
Build With Patched Twisted Build
Fixes TypeError in simulate call

Ref: https://twistedmatrix.com/trac/ticket/9660
Ref: https://github.com/twisted/twisted/pull/1679
2022-01-13 22:23:25 +00:00
tbkizle
560a52a443
Fix OpenSSL For Libtorrent
libtorrent + pyinstaller requires a lib(ssl/crypto)-1_1.dll and
lib(ssl/crypto)-1_1-x64.dll odd quirk but solveable by just having
two copies. Maybe later compiling our own libtorrent.
2022-01-13 22:23:25 +00:00
tbkizle
b9a208f18f
Update Windows Packaging
* Rename instances of win32 to generic win or the appropriate bit where applicable
* Remove files used in GTK2
* Add spec file for use with PyInstaller
* Remove Python bbfreeze Script
* Add Github Action To Build Releases
* Add Modified script to make files used by NSIS
* Update Readme

Closes: https://github.com/deluge-torrent/deluge/pull/331
2022-01-13 22:23:08 +00:00
Calum Lind
6da4c4bf66
Restore PY2 for 3rd-party plugins
Restored PY2 to avoid breaking compatibility with plugins that imported
PY2 from common.

Ref: https://bitbucket.org/bendikro/deluge-yarss-plugin/issues/67/deluge-210-removed-all-py2-support
2022-01-13 19:48:53 +00:00
Calum Lind
d2390cd247
[i18n] Fix load_libintl error
Fixed libintl being undefined if no library was found
2022-01-12 20:19:56 +00:00
Calum Lind
c3cd7f5e5c
[Plugins] Fix missing description with metadata 2.1
Changes to the metadata specs in v2.1 meant that Description field
might appear in the body of the message instead of as a header key.

Replaced custom parser with email parser (as outlined in the document
using compat32 policy) to simplify extracting the message header and
body.

Ref: https://dev.deluge-torrent.org/ticket/3476
Ref: https://packaging.python.org/en/latest/specifications/core-metadata/#description
2022-01-12 20:12:02 +00:00
Calum Lind
2351d65844
[Plugins] Fix and refactor get_plugin_info method
A new metadata version 2.1 has optional Description that is causing an
TypeError when looking up the key in plugin_info since clients are
assuming values are always strings but the default is None.

Fixed TypeError by ensuring that the info dict has a default empty
string set for all keys.

Separated the parsing of the pkg_info into static method to make it
easier to test.

Changed the missing plugin info to only set the Name and Version as
'not available' since all other fields are optional.

Ref: https://dev.deluge-torrent.org/ticket/3476
Ref: https://packaging.python.org/en/latest/specifications/core-metadata/#description
2022-01-12 19:19:39 +00:00
Calum Lind
e50927f575
[GTK] Fix unable to prefetch magnet in thinclient
A UnicodeDecodeError is raised in transfer module when attempting to
prefetch a magnet.

This is result of passing a Python dict containing text bytes and raw
bytes that cannot be decoded as utf-8 in rencode when recieving the
message. This could be handled in rencode by returning raw bytes if
decoding fails (perhaps with a strict mode?) however better to follow
convention of encoding raw bytes in base64 in API calls.

Fixed by retaining bencoding and encoding with base64 when sending
result.

Resolves: https://github.com/deluge-torrent/deluge/pull/334
2022-01-08 19:43:40 +00:00
Calum Lind
79b7e6093f
Fix is_url and is_infohash error with None value
Encountered a TypeError with None value passed to is_infohash function
so add guard clause.
2022-01-08 13:56:05 +00:00
DjLegolas
4f0c786649
[AutoAdd] Fixed error dialog not being shown on error
This happened due to the removal of `exception_msg` attribute, which was
removed with the changes to `RPC` protocol in commit 9b812a4.
Now we access the message using the `message` attribute.

Closes: deluge-torrent/deluge#332
Closes: https://dev.deluge-torrent.org/ticket/3069
2022-01-06 10:06:03 +00:00
DjLegolas
fca08cf583
[TrackerIcon] Fixed old-large icon removal
After downloading and resizing the new icon, we try to remove the downloaded
file, which is larger, but it fails because it tries to do so when the file
is still open, and therefor locked.
On close of the UI, we got `PermissionError` exceptions for each new icon.
2022-01-06 10:04:22 +00:00
DjLegolas
517b2c653b
[TrackerIcon] Fixed parse error on UTF-8 sites with non-english chars
When parsing the site's page in search for the FAVICON, the page gets opens.
The default file encoding in dependent on the running OS, and might not
be `UTF-8` on Windows.
Therefor, some trackers might not get their icon downloaded at all because of
an error:
`UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 2158: character maps to <undefined>`.
This fix adds a detection of file encoding using the optional `chardet` dependency, and also a test.

Closes: deluge-torrent/deluge#333
Closes: https://dev.deluge-torrent.org/ticket/3479
2022-01-06 10:04:19 +00:00
Patrick Byrne
44dcbee5f4
[GTK] Make combobox_window expand to width
This makes the download location entry textbox resizable which is very
useful for entering long paths.

Closes: deluge-torrent/deluge#295
2022-01-03 22:17:00 +00:00
DjLegolas
efc9f465f0
[WebUI] Define foreground and background colors
There is no promise that default bg is white and default fg is black so
define in deluge.css

Ref: https://dev.deluge-torrent.org/ticket/3435
Closes: deluge-torrent/deluge#330
2022-01-03 22:07:11 +00:00
DjLegolas
5321d24f2a
[GTK] Use GtkSpinner when testing open port
this switched was motivated by an error which happened each time the check
port button was clicked, and was caused by the GtkImage when loading the
loading.gif file on Windows:

    cannot register existing type 'GdkPixbufGdipAnim'

Closes: deluge-torrent/deluge#329
2022-01-03 22:02:53 +00:00
RedBearAK
f30f7f4629
[UI] Add Keywords property to desktop file
Deluge fails to appear in some app launchers (GNOME app search, Albert launcher) when searching for just "torrent" or other keywords, rather than "bittorrent". This is due to the lack of a Keywords header/property in its desktop entry file. Adding this line should solve the issue.

I don't know if the underscore "_" is actually necessary for this line, I just copied the appearance of the lines above it when inserting. Please check that this comes out without the underscore in the final file after processing.

Closes: deluge-torrent/deluge#323
2021-12-29 21:54:00 +00:00
DjLegolas
ec0bcc11f5
Upgrade codebase with pyupgrade (>=py3.6)
Added pyupgrade utility with manual stage to pre-commit and run on all
files.

Ref: https://github.com/asottile/pyupgrade
Closes: deluge-torrent/deluge#326
2021-12-29 21:51:07 +00:00
Calum Lind
16895b4a49
[Docs] Fix spinx-contrib-spelling build error
CI docs build was failing with the following error when using latest
sphinx-contrib-spelling 7.3.1

    error: option -j not recognized

Fixed by pinning to previous version.

GitHub-ref: https://github.com/sphinx-contrib/spelling/issues/142
2021-12-29 21:43:03 +00:00
DjLegolas
f3784723ae
[UI] Add SVG support for tracker icons
SVG files are supported by all browsers so need to support it as well,
according to https://www.w3schools.com/html/html_favicon.asp

Also, it appears as SEO.com site, which was dropped because of a cert issue,
has only SVG icon. So enabled it again.

Lastly, from python 3.2, `os.path.samefile` is supported on Windows.
So Windows will now test TrackerIcons as well.
2021-12-29 21:38:55 +00:00
DjLegolas
7f5857296e [CI] Upgrade Windows python version to 3.8 (same as linux) 2021-12-29 20:06:10 +02:00
DjLegolas
897955f0a1
Remove all Python 2 support
* Removed all __future__ imports from code
* Removed all six dependencies
* Removed all future_builtins imports
* Removed all Python 2 related code

Closes: deluge-torrent/deluge#325
2021-12-28 19:26:38 +00:00
Calum Lind
ff309ea4c5
[GtkUI] Fix ETA sorting to match WebUI
The sort for Ascending was putting longest eta first but seems more
intuitive that the smallest time to wait should be first. The WebUI
in previous commit swapped this behaviour so updating GtkUI.
2021-12-22 23:17:14 +00:00
DjLegolas
3b11613cc7
[WebUI] Fixed ETA sorting in WebUI
When sorting the according to ETA values, all torrents with infinite value were being
considered a lower value (INF -> 12 -> 32) instead of largest (12 -> 32 -> INF).
This is due to the fact that the INF symbol is placed to lower value (<= 0).
Now the lower values are being treated as the largest JS number when sorting.

Closes: https://dev.deluge-torrent.org/ticket/3413
Closes: https://github.com/deluge-torrent/deluge/pull/321
2021-12-22 22:04:24 +00:00
DjLegolas
a2d0cb7141
[Console] Removed Core dependency from Console UI
UIs should not depend on core directly, so removing the dependency in ConsoleUI.
This is done by adding a hard-coded variable.

Closes https://dev.deluge-torrent.org/ticket/3491
Closes: https://github.com/deluge-torrent/deluge/pull/320
2021-12-22 21:53:17 +00:00
DjLegolas
88ffd1b843
[Servers] Moved check_ssl_keys and generate_ssl_keys to crypto_utils.py
With this change, we drop a core dependency from the UI. This will help group together
all related functionality in one place, i.e. all security related functions.

Also updated testssl.sh version to 3.0.6 (SECURITY_TEST)

Closes: deluge-torrent/deluge#288
2021-12-20 22:09:08 +00:00
Calum Lind
6a10e57f7e
back to development 2021-12-15 19:43:39 +00:00
Calum Lind
612e0061ed
Release 2.0.5 2021-12-15 18:48:16 +00:00
Calum Lind
2eee7453cb
Update Changelog 2021-12-15 18:45:25 +00:00
Calum Lind
58cc278145
[i18n] Fix set_language error with empty lang string
The Web server config for language was set to an empty string which
resulted in an warning logged by i18n set_language.

* Changed set_language to ignore empty language string and do nothing.
We don't want to override the user's system language unless actually
specified by the user.
* Improved the translation warning message.
2021-12-15 18:35:50 +00:00
Calum Lind
a03e649da6
[Build] Fix WebUI js minifying error
Some users encoutered a bug where WebUI in browser show a white screen,
which indicates a problem with loading javascript files. The problem was
due to closure minifying failure leaving a zero-length deluge-all.js
file which broke the usual fallback mechanism to debug files.

* Fixed usage of ES6 const declaration breaking closure minifying.
* Cleanup minified files upon errors so no zero length files left
* Replaced broken and unmaintained slimit with rjsmin.
* Fixed unable to set dev or debug query args due to request args
requiring bytes.
2021-12-15 09:37:55 +00:00
Calum Lind
073bbbc09d
[Packaging] Start replacing deprecated distutils
Working towards removing distutils

> direct usage of distutils is now actively discouraged,
with setuptools being the preferred replacement.

Ref: https://setuptools.pypa.io/en/latest/deprecated/distutils-legacy.html
2021-12-13 23:57:09 +00:00
Calum Lind
bca0aa3532
[CI] Replace pypi deluge-libtorrent with libtorrent
* Remove certifi since included in requirements.txt
* Remove old travis config
2021-12-12 21:49:31 +00:00
Calum Lind
cb588d0205
[Docs] Update release checklist page 2021-12-12 21:43:54 +00:00
Calum Lind
2b20e9689b
back to development 2021-12-12 19:35:54 +00:00
Calum Lind
65f7cf0d83 Release 2.0.4 2021-12-12 18:57:38 +00:00
Calum Lind
5ac8f4c81b Update changelog 2021-12-12 18:55:41 +00:00
Calum Lind
c33c9082d9 [Docs] Add Flatpak install links 2021-12-12 18:39:41 +00:00
Calum Lind
62ae0f5ef6 [Docs] Update install guide
Rewrite install instructions to include more details on Deluge 2.0
install.

* Added other Linux distros
* Added stable PPA details
* Added link to forum for Windows and macOS community packages

Co-authored-by: Sergio M <sergio@example.com>
Co-authored-by: Ofry Linkovsky <15746116+OfryL@users.noreply.github.com>
Closes: deluge-torrent/deluge#296
Closes: deluge-torrent/deluge#310
2021-12-12 18:27:05 +00:00
Calum Lind
24aa48187e [Docs] Replace recommonmark with MyST parser
We used recommonmark so that we can use markdown in sphinx but it is
buggy and now so switch to better supported MyST-parser.

* Fixed incorrect heading warnings in markdown.
* Added sphinx toctree to markdown using directive as required by MyST.
* Upgraded Sphinx to 4.3

Ref: https://myst-parser.readthedocs.io
2021-12-12 18:16:21 +00:00
DjLegolas
342cca4367 [GTKUI] Open tracker edit with double click
Closes #2434
Closes: deluge-torrent/deluge#253
2021-11-26 07:29:34 +00:00
Unit 193
9194092d7b [GTKUI] Support using the Ayatana fork of indicators.
As this fork is maintained in Debian, and as of Impish/21.10 is the supported
variant in Ubuntu as well.

Closes: deluge-torrent/deluge#317
2021-11-24 19:49:49 +00:00
Andrew Stone
5f6f65a065 [GTKUI] Add "Last Transfer" column
Closes: deluge-torrent/deluge#255
2021-11-23 20:45:09 +00:00
Gregorio Litenstein
967537a409 [GTK3UI] Allow escape key to close Create Torrent dialog 2021-11-23 20:07:44 +00:00
Calum Lind
f74163489c [Packaging] Fix gtk3 glade path in MANIFEST
Missed path update when moving from gtk2 to gtk3
2021-11-23 20:07:33 +00:00
Martin Hertz
7a110bd60f [Plugins] Fix allow enabling any plugin Python version
Properly fix allow enabling any plugin Python version, first attempted
in previous commit 3433a91

Closes: deluge-torrent/deluge#316
2021-11-08 19:27:24 +00:00
Matias Wilkman
d56636426e [GTKUI] Added detection of torrent URL on GTK UI focus
In case deluge GTK gets focus with a new torrent URL on the clipboard,
the "Add Torrent from URL" dialog will pop up automatically

Closes: deluge-torrent/deluge#306
2021-10-03 19:34:09 +01:00
Calum Lind
de4fbd2e82 [Core] Workaround torrent file_progress lt 2.0 error
Workaround lt 2.0 python bindings error when calling a torrent handle
file_progress:

```
Boost.Python.ArgumentError: Python argument types in
    torrent_handle.file_progress(torrent_handle)
did not match C++ signature:
    file_progress(libtorrent::torrent_handle {lvalue}, libtorrent:🎏:bitfield_flag<unsigned char, libtorrent::file_progress_flags_tag, void> flags=0)
```

Should be fixed in 2.0.5 release: https://github.com/arvidn/libtorrent/commit/3feba04e6d
2021-10-03 18:53:31 +01:00
Calum Lind
9c3982d4ff [GTKUI] Fix piecesbar crashing when enabled
When enabled in preferences the piecesbar was crashing the application.

This was narrowed down to an issue with text_font property and there was a
suggestion that the PangoFontDescription should be freed after getting
the result from get_style_context.

Fixed by creating a copy of the PangoFontDescription

Refs:

 * https://trac.wxwidgets.org/ticket/15697#comment:1
2021-10-03 17:20:22 +01:00
Calum Lind
88fc21e993 [Stats] Fix cairo error and failing tests
Fixed the Stats GTKUI test not updated to the new GTK3 dir layout.

Fixed pygobject cairo ImageSurface error:

    AttributeError: 'gi.repository.cairo' object has no attribute 'ImageSurface'

The documentation seems to suggest that using `import cairo` is the correct
usage and this fixed the issue, along with adding suggested gi.require_foreign
call.

References:

https://pygobject.readthedocs.io/en/latest/guide/cairo_integration.html
2021-10-03 14:22:11 +01:00
Calum Lind
54674576db [UI] Remove num_blocks_cache_hits usage for stats
Removed in libtorrent v2: https://github.com/arvidn/libtorrent/commit/569d4
2021-10-03 14:19:40 +01:00
iczero
89189adb24 [Core] Stop using removed disk.num_blocks_cache_hits stat
Removed in libtorrent v2.0.0:
569d47c391
2021-10-03 14:19:17 +01:00
Calum Lind
a5a7da4a1a [Core] Use external_address in external_ip alert handler
Unit tests are segfaulting and this is a result of a subsequent call to
pop_alert creating a dangling pointer to alert.message method.

Replace alert.message call with correct external_address property that
doesn't require any parsing and wonder trigger segfault to expired
pointer.

https://github.com/arvidn/libtorrent/issues/6437
2021-10-03 14:16:57 +01:00
Calum Lind
1e6cc03946 [Lint] Fix spelling mistakes
A quick fix of some of the mistakes caught by codespell.
Updated readme with new IRC server

Useful to add it as part of linting checks.
2021-09-21 21:43:53 +01:00
Calum Lind
d8526ba653 [UI] Add magnet icons for copy and add actions
Added new magnet icons with a consistent naming scheme
Run script to update all icons

Co-authored-by: Matias Wilkman <matias.wilkman@gmail.com>
2021-09-21 20:38:08 +01:00
Gargaj
c38f913948 [WebUI] Add menu option to copy magnet URI 2021-09-21 20:35:24 +01:00
Calum Lind
0659fe4641 [Core] Export torrent get_magnet_uri method
Returns a generated magnet uri from torrent info
2021-09-21 20:27:38 +01:00
Calum Lind
10501db63d [i18n] Update translation PO files from Launchpad 2021-09-14 22:02:56 +01:00
Calum Lind
2a312159b9 [GTKUI] Fix unhandled error with empty clipboard
If the primary clipboard was empty the fallback resulted in an unhandled
error due to missing arguments.

Fixed by using SELECTION_PRIMARY as fallback clipboard
2021-09-14 21:56:03 +01:00
Calum Lind
cb75192df4 [CI] Add core dump capture to GH job
Add further debugging to trace segfaults with lt 1.2
2021-09-10 19:06:21 +01:00
Calum Lind
588f600ba2 [#3310|Core] Change logging invalid session status key to debug
Users were complaining about logs being flooded with `Session status key not valid`
which was a result of the Stats plugin using the wrong status keys.

Fixed by changing to debug log level since not useful in warning level
if spamming.
2021-08-29 16:07:53 +01:00
iczero
ea609cd3e0 [#3310|Stats] Fix constant session status key warnings
Fixed logs flooded with:

    [WARNING ][deluge.core.core              :655 ] Session status key not valid: num_connections
    [WARNING ][deluge.core.core              :655 ] Session status key not valid: dht_cache_nodes
2021-08-29 16:05:36 +01:00
Calum Lind
4b6c7d01b2 [#3478|Core] Fix loading magnet with resume_data and no metadata
Since libtorrent 1.2.10 magnets save resume_data even with metadata not
yet downloaded. Unfortunately when using the deprecated
add_torrent_params key resume_data results in an error  "missing
info-hash from URI"

The problem is due to lt session requiring an info_hash in
add_torrent_params but resume_data does not set or override this key and
resume_data overrides the add_torrent_params.url with an empty string.

The workaround is to specify the info_hash in add_torrent_params. We
require sha1_hash object or bytes and use of bytearray to maintain
python2 compatability.

https://dev.deluge-torrent.org/ticket/3478
2021-08-29 15:58:48 +01:00
Calum Lind
b89b2c45b1 [Console] Fix using windows-curses on Windows
The console tests are still failing on Windows due to an issue where the
sys args are not being correctly replaced in the tests so the pytest
args are being passed to console.
2021-08-01 08:48:27 +01:00
Calum Lind
e38f1173cf [Notifications] Fix email KeyError with status name
Fix user reported error:

    Notification failure using email:
    [Failure instance: Traceback: <class 'KeyError'>: 'name'

This was due to using empty dict used with get_status where a list of
keys is now required or the all_keys parameter is used.

Fixes: #3303
2021-08-01 08:43:37 +01:00
Calum Lind
e1e0999de6 [Tests] Fix skipping torrent test for libtorrent >= 1.2
Test is still failing with libtorrent 2 so also skip.
2021-08-01 08:35:17 +01:00
Calum Lind
5c9378ac5e [Tests] Fix incorrent twisted defer import
With the release Twisted 21.7.0 there was an import error running the
tests due to defer incorrectly imported via twisted.internet.tasks module.
2021-07-31 22:08:23 +01:00
Calum Lind
f075f391cb [CI] Add catchsegv to get a backtrace for segfaults
Encountering random libtorrent segfault with GitHub action so add
catchsegv when running tests to get more information.
2021-07-31 22:08:23 +01:00
Calum Lind
8fb25f71f3 [Install] Update and fix python optional requirements
* Added required dependency setuptools to install_requires
* Remove optional dependency ipaddress from install_requires
* Created extras_require in setup.py. The optional dependencies should
not be included in install_requires so that users can either install
forked dependencies or remove problematic ones. Updated documentation to
detail how to install these optional dependencies.
* Fixed README badge

Refs:
 * https://dev.deluge-torrent.org/ticket/3470
 * https://dev.deluge-torrent.org/ticket/3282
 * https://dev.deluge-torrent.org/ticket/3353
2021-07-31 22:08:23 +01:00
Calum Lind
a3332079db [GTKUI] Remove deprecated GTK attributes 2021-07-25 17:42:05 +01:00
Calum Lind
0d6eec7a33 [i18n] Refactor loading libintl library
Handle different names for libintl library on MacOS and Windows with
fallback.
2021-07-25 16:47:14 +01:00
Calum Lind
f16afc59ba Ignore TypeError with custom Twisted logging
The modification of Python logging _findCaller args in Python 3.8 raises
TypeError in our custom Twisted Logger with Twisted <= 19 versions.

The actual issue for the custom logger was fixed in 18.9 so added a
version check to avoid usage.

Refs:
 - https://twistedmatrix.com/trac/ticket/7927
 - 6b894744e4
2021-07-25 13:28:18 +01:00
Calum Lind
e5388048a9 [CI/CD] Add github actions to replace Travis
Due to new limitations for open-source projects on Travis we are
switching to GitHub actions.

* Notes about system site-packages

We had many problems with accessing system python packages on Travis for
libtorrent and GTK and the problems are harder on Github since there is
no more access. For now copying the python libtorrent binary into the
deluge source is the workaround. There is a pip package that could be
used in future.

Fixed failing tests with libtorrent 1.2 which required a non-zero length
file in torrent and workarounds for async alert delay.
2021-07-25 13:27:26 +01:00
Calum Lind
5374d237a7 [AutoAdd|3295] Correctly fix auto-adding magnets
Properly fix adding magnets, first attempted in previous commit 2e466101fc

add_torrent_magnet does not return a deferred so wrap in maybeDeferred.

Fixed broken test due to new deluge website icon
2021-07-24 11:25:25 +01:00
RFBomb
2e466101fc [AutoAdd] Fix magnet missing applied labels
The autoadd function does not apply labels to torrents that are added via magnet files.
Those magnet files are also renamed ".Magnet.Invalid".

Here are two threads discussing the issue, which still exists.

    https://forum.deluge-torrent.org/viewtopic.php?t=55539
    https://dev.deluge-torrent.org/ticket/3295

Here is what Deluged.log shows when the problem occurs:

21:51:38 [ERROR   ][deluge_autoadd.core           :333 ] Cannot Autoadd magnet: /Torrents/TorrentFiles/FileName.magnet: Torrent already in session (e1e0f33b656cb74532dcddc04f2ec52771ef1c26).
21:56:38 [ERROR   ][deluge_autoadd.core           :333 ] Cannot Autoadd magnet: /Torrents/TorrentFiles/FileName2.magnet: Torrent already in session (ef839d84d113cc35719b6fd616a4d8e220de7d32).

After looking at the code, what appears to be happening is the magnet link is added, but then a second scan of the folder occurs. Since the magnet file was never renamed, it will attempt to add it again, error out, then rename the file "magnet.invalid".

The only difference between the torrents working properly and magnets having the issue is the two lines I copy-pasted into the magnet IF statement. This should resolve the issue.
2021-04-17 17:24:58 +01:00
DjLegolas
8676a0d2a0 [Core] Improve on_alert_tracker_error for lt >= 1.2
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.  if there is at least one working, it will not set
the tracker status to
error and set it to `Announce OK`. otherwise, it will use the error
message from the alert.

Refs: https://dev.deluge-torrent.org/ticket/3384
2021-04-17 16:55:54 +01:00
Calum Lind
3ec23ad96b [#3388|WebUI] Fix md5sums in torrent files breaking file listing
Torrents containing md5sum optional hashes are not being decoded and so
causes errors in the json_api when the TorrentInfo is returned:

    Object of type bytes is not JSON serializable

Fixed by removing all optional hashes from the paths returned from
TorrentInfo and only including the required path keys. The optional
hashes are unused by Deluge so simplify by removing.

Fixed Windows path issue in TorrentInfo by ensuring conversion to posix paths.

Refs:

http://wiki.bitcomet.com/inside_bitcomet
http://wiki.depthstrike.com/index.php/P2P:Protocol:Specifications:Optional_Hashes
https://wiki.theory.org/index.php/BitTorrentSpecification
2021-03-24 10:26:08 +00:00
Hans Ole Hatzel
dcd3918f36 [WebUI] Add test for torrent files containing md5sums
Some torrent files built with py3createtorrent fail to produce a
file listing in the WebUI when uploading them.
This made it impossible to add such files.
Specifically this is caused by the additional metadata when using
py3createtorrent with the `--md5` flag.
2021-03-24 10:26:08 +00:00
Calum Lind
08c7f1960f [CI/CD] Fix Tox SSL error in Windows Travis job
The tox sdist-make step failed with SSL: CERTIFICATE_VERIFY_FAILED so
fix by install certifi to ensure updated SSL certificates are available.
2021-03-24 10:25:30 +00:00
Calum Lind
8a4ec493c0 [CI/CD] Add Travis windows build
* Added APPDATA to tox passenv so it is available to common module.
* Fixed windows path issue in httpdownloader tests
* Skipped torrentmanager test due to the following error from loading a
Linux pickled state file with a different line ending.
    ModuleNotFoundError: No module named 'deluge.core.torrentmanager\r'
* Removed appveyor build
2021-02-23 10:41:46 +00:00
Calum Lind
4d970754a4 [#3440] Fix httpdownloader reencoding torrent file downloads
Torrent downloads from rutracker responds with the header:

    Content-Type: application/x-bittorrent; charset=Windows-1251

The problem is that httpdownloader was using the charset to re-encode
the downloaded file, corrupting the binary torrent file download.

Fixed by only re-encoding text content types, since it is very rare
that non-text content types would actually have a non-utf8 codeset and
if there is a requirement we would need to determine it on a type by
type basis.
2021-02-20 21:18:32 +00:00
Calum Lind
f331b6c754 [GTKUI] Fix torrentdetails tab bar position not saving
The GTKUI tests were failing and the saved config for the tab bar
position was not being restored.

Fixed by moving the setting of notebook.tabs_pos to TorrentDetail init.

Replaced more deprecated methods that were showing up in tests.
2021-02-20 17:29:36 +00:00
Calum Lind
1022448e4f [#3441|GTKUI] Add a torrentdetails tabs position menu
The tabs placement for the torrentdetails notebook might not be to
everyone's liking so add a menu item to configure it.

Default the position back to top.
2021-02-20 15:22:08 +00:00
Calum Lind
291540b601 Hide pygame community banner in console
Notifications plugin uses pygame for sound notifications however pygame
show a console message "Hello from the pygame community." whenever
starting deluge from console.

Refs: https://stackoverflow.com/a/55769463/175584
2021-02-20 14:11:14 +00:00
Calum Lind
092d07b68e [#3337|Core] Fix lt listen_interfaces not comma-separated
A typo meant that the interfaces supplied to libtorrent were not
comma-separated.

Refs: https://github.com/arvidn/libtorrent/blob/RC_1_1/include/libtorrent/string_util.hpp#L70
2021-02-06 17:26:30 +00:00
Calum Lind
da5d5bee20 [#3325|Core] Fix unable to remove magnet with delete_copies enabled
Users were encountering the following error while attempting to delete
magnet torrents and had the config 'Delete copy of torrent file'
enabled. This was due to removing a magnet before the metadata was
downloaded and the torrent.filename was still set to None so raises
exceptions when string operations are performed with it.

  File "/usr/lib/python3/dist-packages/deluge/core/torrent.py", line 1317, in delete_torrentfile
    os.path.join(self.config['torrentfiles_location'], self.filename)
  ...
  TypeError: join() argument must be str or bytes, not 'NoneType'

Fixed by both setting a default empty string for self.filename and only
deleting the torrent file copy if filename is set.
2021-02-05 20:33:19 +00:00
Vasilij Schneidermann
6d9dc9bd42 [WebUI] Add country flag alt/title for accessibility
This allows viewing the country in textual form by hovering the flag
image and displays it if the image couldn't be loaded.
2021-01-29 18:49:24 +00:00
bendikro
937afd921c [Tests] Fix console tests sometimes failing due to hard coded port
The tests in ConsoleUIWithDaemonBaseTestCase could fail to the hard coded
port 58900 being busy.
Fix by using the proper port found in self.listen_port

Also convert unnecessary use of assertTrue where more appropriate
assert functions should be used, such as assertEqual and assertIn
2021-01-29 18:46:42 +00:00
EFS
a4da8d29f8 [WebUI] Fix tracker icon download error
Encoutering an error when webui attempts to download tracker icon:

    Error occurred downloading file from "http://b'acg.rip'/": invalid
    hostname: b'acg.rip'

Fixed by ensuring the request.tracker_name is decoded from bytes before
looking up the icon name.
2021-01-29 18:31:52 +00:00
neeshy
8ec5ca9d08 [Console] Fix setting 'Skip' priority on console
Selecting priorities 'Low' and 'Skip' on console will both set the
actual priority to 'Low'.

Fixed typo in previous commit 6655fe67c3
2021-01-29 18:26:35 +00:00
scudre
9c90136f57 [#3439] Execute plugin fails to run on Windows
Fixed TypeError: a bytes-like object is required, not 'str'
2021-01-29 18:17:35 +00:00
Calum Lind
610a1bb313 [Lint] Update pre-commit hook and isort versions
* Fixed black hook requiring Py3.6 to installed locally. Will now assume
Py3.6+ in installed.
 * Added isort traceback in pre-commit flake8 hook fails
 * Updated versions of Black, Prettier and isort
 * Keep Flake8 at 3.7.9 due to E402 issue: https://gitlab.com/pycqa/flake8/-/issues/638
 * New pyproject config for isort v5 with fixes for Python 2 imports.
 * Fixed travis config to run Python 3.6 for lint run. Replaced the
virtualenv with_system_site_packages config with Travis specific Python
config value so lint run doesn't attempt to append with_system_site_packages
to Python 3.6 command.
2021-01-24 20:40:20 +00:00
Calum Lind
23a48dd01c [#3309|GTK] Fix cmp function for None types
Comparisons on Python 3 are much stricter resulting in the following
error comparing with None:

    TypeError: '>' not supported between instances of 'NoneType' and 'str'

Fix this by getting the type of the other value and getting it's default
value.
2020-04-30 14:30:37 +01:00
Calum Lind
d02fa72e80 [Console] Fix hostlist status lookup errors
If a host in hostlist failed DNS lookup or other issue it was returning
a tuple instead of deferred. Fix this in hostlist by returning a
defer.succeed.

A race condition with BaseMode was also encountered when
update_hosts_status calls update_select_host_popup and
ConnectionManager does not have a rows attribute. Fix this by init
BaseMode before update_hosts_status and remove already called
update_select_host_popup.
2020-04-27 16:24:33 +01:00
Calum Lind
62d8749e74 [#3348] Fix TypeError adding peers to torrents
Python3 has stricter type checking and passing a port as string results
in libtorrent raising a TypeError.

Fixed by casting port to int, along with refactoring to ensure ipv6 is
correctly parsing and a useful error is output to user with invalid ip
or port details.

https://dev.deluge-torrent.org/ticket/3348
2020-04-25 13:16:04 +01:00
Nitzan Raz
76f0bf2e04 Ctl+Q to quit Deluge GTK without killing daemon 2020-04-25 13:08:54 +01:00
Calum Lind
635f6d970d [Config] Fix loading config with double-quotes in string
If a password or other string contained a double-quote then the config
would fail to be loaded on startup and reset.

This occurred due to fixing a similar issue with curly braces for #3079
in commit 33e9545cd4 and the checking for double-quotes had unforseen
consequences.

To resolve both these issues the code to check for json objects in
config files was simplified and utilises the json module raw_decode
method to ensure the extracted string indexes are json objects.
2020-04-25 10:49:08 +01:00
bendikro
672e3c42a8 [Tests] Add pytest markers to tox.ini
Remove pytest warnings due to unknown markers
2020-04-23 17:19:37 +01:00
bendikro
c1110e4ef3 [Tests] Fix tests failing when deluged fails to listen
Commit b32c5d824 changed the logged message in deluge/core/daemon_entry.py
when libtorrent fails to listen on the given port, without updating the
trigger expression in deluge/tests/common.py:start_core to match the new output.

Fix by updating the trigger match expressions to match the new log output
2020-04-23 17:19:37 +01:00
bendikro
742c8a941a [Tests] Fix PytestDeprecationWarning from pytest
Accessing pytest.config is deprecated and produces:
PytestDeprecationWarning: the pytest.config global is deprecated. Please use
request.config or pytest_configure (if you're a pytest plugin) instead.

Fix by using a pytest.fixture
2020-04-23 17:19:37 +01:00
bendikro
3427ae4b90 [GTK] Remove PyGIWarning in gtk3/files_tab.py
Remove warning: PyGIWarning: Gtk was imported without specifying a version first
2020-04-23 17:17:20 +01:00
bendikro
034db27936 [GTK] Add more width to outgoing ports spinbuttons in network preferences
The spinbuttons would sometimes be truncated.
Fix by increasing the width
2020-04-23 17:17:20 +01:00
bendikro
1e3c624613 [GTK] Destroy the dialog before running the callback
Currently, the dialog window is displayed until after the callback has returned.
The result is that if a new dialog is opened from the callback, the first dialog
is still displayed until the new dialog is destroyed.

Fix by destroying the dialog before running the callback.
2020-04-23 17:17:20 +01:00
bendikro
3519f341d4 [GTK] Fix showing correct error on libtorrent import error
The exception string "No module named libtorrent" was changed to
"No module named 'libtorrent'" in python 3.3, which results in a
"unknown Import Error" message being displayed instead of the
message meant for libtorrent import error.

Change to raising LibtorrentImportError in _libtorrent.py and
catch this error to display libtorrent specific import errors.
2020-04-23 17:17:20 +01:00
neeshy
d6c96d6291 Fix warning related to gettext 2020-04-23 17:14:24 +01:00
Alex Knaust
15c250e152 Fix template config.ui naming in create_plugin script. 2020-04-20 00:01:16 -07:00
Calum Lind
eb57412601 [Tests] Fix tox, pytest and travis issues
* Error occurring with Pytest 5.4 so pin to below that version.
 * Fix minor issues with Travis config.
 * Use full command-switches for pytest in tox config.
 * Remove pin for pip as issue with pip-wheel-metadata was fixed in 19.3
 * Remove tox-venv as causing issues of incompatible packages installed.
   The latest versions of the virtualenv package should handle these
   duties.
2020-04-12 17:37:42 +01:00
Calum Lind
2f1c008a26 [Console] Fix AttributeError setting config values
GitHub user JohnDoee reported that config settings are not decoded
correctly, this error can be reproduced with a command like

    deluge-console -c /config/ "config --set download_location /downloads"

    > AttributeError: 'str' object has no attribute 'decode'

The tokenize code was using 'string-escape' to decode strings but there
is no direct replacement in Python 3 but also unnecessary. However the
tokenize code is complex and buggy and not really suitable for the task
of evaluating config values.

A better alternative is to evaluate the config values using the json
decoder with some additional logic to allow for previous syntax usage,
such as parentheses.

Added a comprehensive set of tests to check for potential config values
passed in from command line.
2019-11-28 12:38:02 +00:00
minus
5e06aee5c8 [Logging] Fix findCaller with unknown source
In case no source was found, a 3-tuple was returned instead of a 4-tuple
in Python 3
2019-11-19 17:44:48 +01:00
minus
351664ec07 [Logging] Fix Python 3.8 compatibility
Deluge's logger class extends Python's `logging.Logger`. Since Python
3.8, it takes an additional argument `stacklevel`.
The implementation in Deluge does not support that. Work around the
problem by ignoring additional arguments.
2019-11-19 17:44:47 +01:00
Calum Lind
5f1eada3ea [Tests] Skip buggy pytest 5.2.3
Plugins test fails due to:

https://github.com/pytest-dev/pytest/issues/6194
2019-11-15 20:55:22 +00:00
Calum Lind
bde4e4443e [Lint] Fix Black and Flake8 issues
For a single element unpack black now also encloses with parentheses to
make it clearer: https://github.com/psf/black/issues/1108

Fix flake8 warnings
2019-11-13 15:44:46 +00:00
Anders Jensen
ed4bc5fa17 [Core] Fix potential "dictionary changed size during iteration" on shutdown 2019-11-12 15:40:38 +00:00
Calum Lind
20afc31f3c [Docs] Fix changlog symlink and markdown issue 2019-11-12 15:37:12 +00:00
t0obz
9232a52fd6 [Docs] Update dev environment instructions
I'm going through these instructions on a clean Ubuntu 19.04 VM

These are the changes I needed to make to get Deluge to build/run
2019-11-12 15:36:52 +00:00
Calum Lind
23b3f144fc [#3298|Core] Fix pickle loading non-ascii state error
When trying to load a torrents.state from version 1.3 users were
encountering the following error:

    UnicodeDecodeError: 'ascii' codec can't decode byte

This was due to the way that Python 2 was pickling state with torrent
filenames that contained non-ascii characters and Python 3 was
unpickling the state using ascii encoding and failing. The fix is to
specify utf-8 encoding when loading torrents.state.
2019-11-12 15:21:56 +00:00
Calum Lind
89d62eb509 [GTK] Remove orphaned code
Changes were made to sidebar theming in commit 5a6f202 and this code was
forgotten to be removed.
2019-10-31 10:56:08 +00:00
Christopher Beard
00176ee2cd [Docs] Typo corrections in testing.md 2019-10-31 10:04:30 +00:00
Pere Orga
8737005b82 [GTK] Fix typo in preferences language label 2019-10-31 10:01:40 +00:00
Jack O'Sullivan
d08c3f72e9 Fix privilege dropping when setting process ownership
`os.setgid()` should be called to set the GID, and it should be called
before `os.setuid()` to prevent reinstatement of privileges.
2019-10-31 09:57:33 +00:00
Calum Lind
40ebdf3f39 [GTK] Add missing translation markup
Found some text needing marked for translation.
2019-10-31 09:40:59 +00:00
Calum Lind
eeeb7fb69b [GTK] Fix Status tab download speed and uploaded
Previous work on the status tab caused these labels to not be in the
correct position so this commit swaps them back..
2019-10-31 09:38:37 +00:00
DjLegolas
3f9ae33793 [Label] Fix Options/Add windows not reopening
When a user clicked ESC key or X button, the Options and Add windows
didn't open again. This happened because the windows were closed and
not hidden, which deleted the instance of those windows.

This fix changed the behavior of the close action to 'hide'.
2019-06-25 11:51:59 +01:00
DjLegolas
0c7f53e305 [WebUI] Fix class-header for Deluge.EditTrackersWindow 2019-06-25 11:51:59 +01:00
Calum Lind
63a4301a8b [Notifications] Fix unhandled TypeErrors on Python 3
- Notify requires GLib.Variant for set_hint
- Twisted defer.fail only accepts Exceptions.

Fixes: #3267
2019-06-25 10:44:51 +01:00
DjLegolas
1b4ac88ce7 [Common] Fix creation of pidfile via command option
Python 3 raises a TypeError for binary file mode and writing text string.

Fixes: #3278
2019-06-25 10:39:49 +01:00
Calum Lind
4b29436cd5 [Core] Fix for peer.client UnicodeDecodeError
Some users have been reporting unhandled UnicodeDecodeErrors and the
traces show it occuring in the call to `peer.client`. Although unable to
replicate it seems prudent to put a try..except around the call to
ensure it does not break the UIs.

Refs: https://github.com/arvidn/libtorrent/issues/3858

Closes: #3279
2019-06-24 16:34:15 +01:00
int3l
833b5a1f30 [Common] Fix show_file unhandled dbus error
If dbus org.freedesktop.FileManager1 service is missing then show_file
raised an unhandled exception. The service is not available on certain
desktop environments e.g. i3wm.

The solution is to fallback to xdg-open.

Fixes: #3272
2019-06-24 11:44:29 +01:00
DjLegolas
24b094a04a [WebUI] Handle torrent add failures
Closes #2253.
2019-06-21 09:09:12 +03:00
Calum Lind
3365201011 [Docs] Fixes for spelling
Running on Ubuntu Xenial results in spelling warnings so update wordlist.
2019-06-18 09:07:48 +01:00
Calum Lind
c1ba403d4e [Docs] Add service how-tos 2019-06-18 09:07:48 +01:00
Calum Lind
8b62e50eb8 [Docs] Add spellchecking with pyenchant
- Use sphinxcontrib.spelling with custom wordlist.
- Skip the checking of the modules documents as they raise
false-positives.
- Add a setup.py spellcheck_docs command.
- Fix spelling and other issues.
- Add a doc favicon.
2019-06-15 21:06:27 +01:00
Calum Lind
5b315e90c5 [Docs] Cleanup updating plugin page 2019-06-15 21:01:04 +01:00
Calum Lind
b711cd258a Release 2.0.3 2019-06-12 18:47:11 +01:00
Calum Lind
e1c4069a72 [Gtk] Refactor presenting window
Include the correct usage for other display servers.

Still not sure how to get the proper timestamp for Wayland or Quartz but
I read that using 0 equals the GDK_CURRENT_TIME which suffices for now.
2019-06-12 16:57:48 +01:00
Calum Lind
a2dee79439 [GTK] Improve detecting X11 display server
GdkX11 still imports on Wayland so check display server is X11 before
importing.
2019-06-12 16:05:15 +01:00
Calum Lind
7a54db3179 [Docs] Fix typo and url for Windows install 2019-06-12 14:56:21 +01:00
Calum Lind
03e7952d26 [GTK] Only import wnck on X11 display
Wnck is only supported on X11 and raises errors in Wayland so only load
it when X11 present.

Fixes: #3265
2019-06-12 10:21:23 +01:00
Calum Lind
7ee8750be4 [GTK] Fix peers tab flag tooltip error
Hovering over a country flag resulted in an AttributeError.

This is due to get_tooltip_context now returning a bool value instead of
the tooltip object.

Fixes: #3219
2019-06-12 09:40:51 +01:00
Calum Lind
f61001a15d [GTK] Fix missing argument for GtkMenu.popup()
Missed while converting from pygtk to Gtk3

Fixes: #3266
2019-06-12 09:40:14 +01:00
Calum Lind
86ddadacf7 [Extractor] Fix startup error
On Python 3 need to create a copy of the dict to iterate

Fixes: #3264
2019-06-12 09:40:14 +01:00
Calum Lind
632089940c [Web] Fix unable to change password
The hashlib update method requires bytes and raised a TypeError for salt
passed as text.

Added a test for auth change_password

Fixes: #3262
2019-06-11 20:14:11 +01:00
Calum Lind
5d7db3e727 [Web] Change request.base path encoding to utf-8
A user reported a problem with setting base path resulting in this error:

    encoding with 'idna' codec failed (UnicodeError: label too long)

It is likely the base path is longer than 63 chars, which is unusual,
however the idna codec is for domain name not paths so switch to utf-8.

Fixes: #3261
2019-06-11 20:14:11 +01:00
Calum Lind
4dd1f63b8b [Web] Fix TypeError with reverse proxy x-deluge-base header
The request header needs decoded otherwise string comparisons fail.

Fixes: #3260
2019-06-11 20:14:11 +01:00
Calum Lind
fc134cdffb [Docs] Add more info to release notes 2019-06-11 20:14:11 +01:00
Calum Lind
36cb4c5a4f [Docs] Updates to release checklist 2019-06-11 20:14:11 +01:00
Calum Lind
676bdb26e0 [Docs] Remove incomplete Windows install instructions
The instructions are in-progress and missing steps so instead point to
the issue ticket for now.
2019-06-11 12:35:04 +01:00
Calum Lind
dff778ceeb [i18n] Try loading intl.dll on Windows 2019-06-11 12:35:04 +01:00
Trav Easton
bdadd2b515 Fix typo in install instructions for macOS 2019-06-11 12:35:04 +01:00
thelamer
a34543100c [Web] Fix peers tab failing to set flag location
The request.country returns bytes not a string so decode.
2019-06-11 12:35:04 +01:00
Calum Lind
b8b044f451 [lint] Fix pre-commit config key name 2019-06-10 14:24:47 +01:00
Calum Lind
2d87cde887 Make a 2.0.2 release 2019-06-08 21:34:27 +01:00
Calum Lind
212efc4f52 [Packaging] Move user out of systemd files and add to tarball
With the `deluge` user specified in the unit files it ties it to
that user and makes it unavailable for re-use by systemd user instance.

Remove the user and group from the unit files and put them in a separate
`user.conf` file that should be installed as an override file e.g. for
deluged.service this would be placed as follows:

    /etc/systemd/service/deluge.service.d/user.conf

Add the systemd files to the tarball for package maintainers.

Closes: #2034
2019-06-08 21:31:49 +01:00
Calum Lind
879a397215 [Packaging] Add updated launchd scripts
Copy from Trac UserGuide and updated with proper naming and deluge-web
version.

The bin location is default for brew with pip install.

Closes: #3073
2019-06-08 21:31:49 +01:00
Calum Lind
957cd5dd9c [Core] Fix SimpleNamespace on Python2 2019-06-08 21:31:49 +01:00
Calum Lind
25087d3f2d [Docs] Add release notes and update pages 2019-06-08 16:42:25 +01:00
Calum Lind
d24109f0a2 Update Changelog for 2.0.1 2019-06-07 20:27:02 +01:00
Calum Lind
647baebcf0 [Docs] Update release checklist 2019-06-07 14:47:49 +01:00
Calum Lind
98ce3cd385 [Packaging] Create tar.gz with sdist for PyPi
PyPi does not accept `tar.xz` source tarballs!
2019-06-07 14:47:02 +01:00
Calum Lind
0c87d9bd7d [Packaging] Fix get_version with no git command
An unhandled FileNotFoundError was encounted if git command was not available.
2019-06-07 14:45:49 +01:00
Calum Lind
aa35247e95 Back to dev 2019-06-06 17:27:49 +01:00
Calum Lind
0dc4e18ac4 Updates for 2.0.0 release 2019-06-06 17:12:58 +01:00
Calum Lind
d4185505d1 [Docs] Cleanup changelog and docs 2019-06-06 17:12:58 +01:00
Calum Lind
04e58659fe Update translation files 2019-06-06 16:50:16 +01:00
Calum Lind
5e738cf73a Simplify the get_version method
Use post segment instead of dev for non-dev tags.
Default to 'deluge-' and '.dev0' to simplify getting version.
Refactor to use subprocess.check_output
Use deluge.common.get_version as fallback in docs conf.
2019-06-06 11:30:45 +01:00
Calum Lind
ce8595e8dd [Tests] Remove python2 from tox config 2019-06-06 11:30:45 +01:00
Calum Lind
be74d96c6a [Core] Copy lt alerts to avoid segfaults
Changes in libtorrent 1.1 mean that alerts are no longer allowed to be
accessed after the next call to pop_alerts.

> It is safe to call pop_alerts from multiple different threads, as
long as the alerts themselves are not accessed once another thread
calls pop_alerts. Doing this requires manual synchronization between
the popping threads.

The solution is to copy the alert attributes and pass that to the
handlers.

Refs: https://github.com/arvidn/libtorrent/issues/2779
      #3159
2019-06-05 15:10:35 +01:00
Calum Lind
4212bd6800 [Travis] Add unit test for libtorrent 1.2
Skip test_torrent_error_resume_original_state on libtorrent 1.2 as it
is failing for an unknown reason.

Refs: #3255
2019-06-05 15:09:40 +01:00
DjLegolas
d40d40af31 [Core] Update to support libtorrent 1.2
Some changes between lt 1.1 and 1.2 require updates to core code.
 - Switch from proxy_type to proxy_type_t
 - Replace hardcoded flag value with add_torrent_params_flags_t since
   1.2 uses different flag values.
 - add_torrent_params requires flags set instead of dict values.

Refs: #3255
2019-06-05 15:09:00 +01:00
Byeonghoon Yoo
cbf9ee8978 fix blurry icons in gnome
Fixed blurry icon by changing "StartupWMClass" value from "Deluge" to "deluge" in "deluge.desktop".
2019-06-04 17:39:06 +01:00
Calum Lind
7abeb4ee0f [packaging] sdist use xztar and exclude .mo files 2019-05-24 11:21:35 +01:00
Calum Lind
bd4a3cba38 [Docs] Update install details and add more pages 2019-05-23 15:41:58 +01:00
Calum Lind
3cfa39a2ad [macOS] Fix GTK windowing issues
On macOS the Quartz windowing is used instead of X11 so make ensure
that the X11 window calls are optional.

Also if gtkosx_application is not available then don't create osxapp.

It would be useful to find out how to pass window timestamps on Quartz.
2019-05-23 13:00:02 +01:00
Calum Lind
a3b6d8d8e5 [Appveyor] Switch to Python 3.6
- Change build from Python 2.7 to 3.6 64-bit.
 - Specify py36 for tox since using py3 will choose latest py3 e.g. 3.7!
 - Use python 3.6 libtorrent.pyd build
 - Use pre-installed OpenSSL 1.1 (matches libtorrent build)
 - Add python version output to tox.ini for debugging.
2019-05-23 00:15:49 +01:00
Calum Lind
7e3692bb5a [Docs] Fix missing path in RTD config 2019-05-22 18:01:05 +01:00
Calum Lind
aa3a9a15cc [Docs] Update readthedocs config
- Add the missing package install so pkg_resources can find version.
2019-05-22 17:48:33 +01:00
Calum Lind
0f92ea401f [Travis] Remove Python2 tests 2019-05-22 14:14:44 +01:00
Calum Lind
260d55aeae [Travis] Fix getting version from git tags
Travis clones the git repo with a shallow depth and the git describe
command cannot find the version tag. Setting depth to 1000 should be
enough but can be increased if required.

Refs:
  https://docs.travis-ci.com/user/customizing-the-build/#git-clone-depth
  https://stackoverflow.com/a/51727114/175584
2019-05-22 12:12:54 +01:00
Calum Lind
a9609a197d [Docs] Fix ReadTheDocs config path 2019-05-22 11:39:37 +01:00
Calum Lind
a8fac1381b [Packaging] Cleanup README for Pypi
- Set a minimal Python version 3.5 and remove universal wheels.
- Tidy up the README
- Add Project URL for issues and docs.
2019-05-22 11:15:32 +01:00
Calum Lind
65f6ede8b2 [Docs] Updates and fixes to build on Python 3
- Updates to the sphinx conf
  - Applied Mock fixes to build on Python 3.
  - Group patches at bottom of conf file.
  - Use just a major.minor for version.
  - Specify Sphinx 2.0 version requirement.
- Move requirements.txt to docs dir.
- Add readthedocs config
- Fix docstring code block rst formatting issue.
2019-05-21 15:23:45 +01:00
Serg
515dbcc5d9 Minor updates to log.py 2019-05-20 21:23:20 +01:00
Calum Lind
827987fe7d [GTK] Fix drag and drop files in files_tab
Encoutered an error reordering files by dragging in the files tab:
   TypeError: can't pickle TreePath objects

The issue was get_selected_row  now returns a list of TreePath objects which
cannot be pickled. Also the set_text method only accept unicode text to
pickled bytes cannot be used.

The fix is to convert the TreePaths to strings and use json to encode
the list of strings for set_text.
2019-05-20 21:14:42 +01:00
Calum Lind
1357ca7582 [i18n] Ignore non-translation dirs in get_languages
The `__pycache__` dir was showing up in list of available languages so
ensure only those directories with languages are returned.
2019-05-20 21:02:13 +01:00
Calum Lind
72d363968e [Logging] Fix line numbers missing on Python 3
The findCaller method returns a 4-element tuple on Python 3 whereas it
was a 3-element tuple on Python 2.
2019-05-20 16:49:49 +01:00
Calum Lind
c6b6902e9f [Core] Fix prefetch magnets missing trackers
When adding magnets that have been prefetched the tracker details were
lost. A result of returning only the lt.torrent_info.metadata which
does not contain full torrent details, such as trackers.

- Modified torrentmanager prefetch_metadata to return dict instead of
  base64 encoded bencoded metadata dict...
  - Used a namedtuple to ease identifying tuple contents.
  - Updated tests to reflect changes with mock trackers added to
    test_torrent.file.torrent.

- Refactor TorrentInfo to accept dict instead of bytes and add
  a class method to accept metadata dict with lists of trackers.
  - Rename class arg from metainfo to torrent_file, matching
    lt.torrent_info.
  - Rename metadata property to correct name; metainfo.
  - Simplify class variable naming with _filedata and _metainfo for
    torrent file contents encoded and decoded respectively.

- Update GTK Add torrent dialog to pass trackers to TorrentInfo.
2019-05-20 16:49:25 +01:00
Calum Lind
6a5bb44d5b [Core] Write torrent file with full magnet metainfo
The use of torrent_info.metadata misses saves trackers and other torrent
metainfo fields so use lt.create_torrent generate method to create this
data.
2019-05-18 16:15:27 +01:00
Calum Lind
cbcf8eb863 [GTK] Fix missing magnet trackers with Add dialog (non-prefetch)
When adding magnets (without prefetch) the trackers were missing.

The issue was that the magnet uri was being xml escaped twice so that
the ampersand was still escaped when passed to core and everything after
the magnet info_hash was ignored.

Also found unneeded call to core.add_torrent_files when only adding
magnets with core.add_torrent_magnet so check torrents_to_add before
calling.
2019-05-18 16:15:27 +01:00
Calum Lind
09cfd9b89e [bencode] Fix unhandled TypeError with string
Passing a non-bencoded dict to bdecode resulted in an unhandled
TypeError.

- Catch TypeError in decode_func.
- Add bdecode tests.
- Replace KeyError and IndexError with base LookupError.
2019-05-18 16:15:27 +01:00
Calum Lind
b961e11df6 [GTK] Fix handling failed magnet prefetching in Add dialog
If a torrent already exists when trying to prefetch magnet metadata the
exception is not handled and dialog does not update correctly.
2019-05-18 16:12:08 +01:00
Calum Lind
2ca683e8fe [Daemon] Fix showing translation warning messages
Disable the warn_msg in daemon_entry since the argparse option are all
marked for translation and warnings are being output. The original
intent of this warn_msg was to identify anything in core code that was
incorrectly marked for translation.
2019-05-17 10:32:07 +01:00
Calum Lind
fd20addead Raise Twisted minimum version to 17.1
The use of CertificateOptions with raiseMinimumTo requires this new
minimum version so update requirements and documents.
2019-05-17 10:08:48 +01:00
Calum Lind
535b13b5f1 [Plugins] Convert plugins to deluge_ module prefix convention
This commit reverts namespace for the plugins and uses a module prefix
"deluge_" in it's place. The distribution package name remains the same
for now but will also be considered to use a prefix to help find the
third-party plugins e.g. Deluge-{Plugin} and the pluginmanager will
strip the prefix for displaying.

The change is a result of problems trying to package Deluge with
pyinstaller and the pkg_resources namespaces is not compatible.
Testing alternatives to using the pkgutil or PEP420 (native) namespaces
did not yield any joy either as importing eggs with namespaces does not
work. [1]

At this point importable eggs are considered deprecated but there is no
viable alternative yet. [2]

[1] https://github.com/pypa/packaging-problems/issues/212
[2] https://github.com/pypa/packaging-problems/issues/244
2019-05-15 19:20:08 +01:00
Calum Lind
d6a0276a78 Update gitignore file
- pip install creates dist-info directories
- add __pycache__ for compelteness and other byte compiled file types.
2019-05-14 11:48:58 +01:00
Calum Lind
9c0325b129 [Plugins] Remove stray unneeded init files 2019-05-13 18:55:35 +01:00
Konstantin Khukalenko
f885edd7fc [Console] Add move completed option to add torrent command
- Added a -m|--move-completed option for specifying a move completed
  path when adding a torrent.
- Re-used existing console test and renamed for generic usage.
- Moved setup_translation to tests.__init__ so it is always setup
  instead of relying on tests.common import.

Closes #2847

Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
2019-05-11 22:33:18 +01:00
Calum Lind
2b171e58a3 [Web] Fix missing deregister_object
pluginbase has the complementary deregister_object but the actual
method was missing in JSON component.
2019-05-11 21:04:34 +01:00
Calum Lind
d417c4b0f9 [Core] Refactor the base argparser and translation code.
- Move baseargparser out of deluge/ui since it is also used by the
  Daemon and could cause packaging issues if UI code is not available.
  - Renamed baseargparser to argparserbase to follow existing Deluge
    naming.
  - Renamed get_version to distinguish from deluge.common.get_version.
- Translation code is usable by more than just the UIs so also move it
  to Deluge namespace and re-use i18n directory and make it a package.
  - Renamed setup_translations to singular as it felt more correct.
  - Renamed set_dummy_trans to be more descriptive.

Closes: #3081
2019-05-11 20:40:20 +01:00
Calum Lind
653f80eac8 [#3250|Console] Fix errors dispaying magnets with no files
Trying to display the files for magnet that has no metadata and thus no
files resulted in `'NoneType' object is not iterable` errors.

Only call file prio update method if we have a list of files.
2019-05-11 19:09:44 +01:00
Calum Lind
76b89a7943 [#3250|Console] Fix unable to remove torrent
Trying to remove a torrent had no effect and resulted in the following
error: `'TorrentList' object has no attribute 'clear_marked'`

 * The clear_marked call needs to be made with torrentview instead.
 * Ensure the popup dialog is closed upon deleting the torrent.
2019-05-11 19:05:42 +01:00
DjLegolas
6ff7a5400f Remove detox development requirement
As of tox 3.7, detox is no longer needed as tox added a native
parallel environment execution.
2019-05-09 12:02:59 +01:00
Calum Lind
db021b9f41 [#3244|Web] Add support for accept-encoding header
* Use EncodingResourceWrapper to replace compress function so that the
proper checks for accept-encoding header are made.
* Ensure only text is compressed and images are left uncompressed.
2019-05-09 11:41:00 +01:00
Calum Lind
ab4661f6fd [Packaging] Remove distro from setuptool requirements
The distro package is only available for Ubuntu 18.04 onwards so don't
require it as it will runtime error about missing module.
2019-05-09 09:20:29 +01:00
Calum Lind
396cadefda [Tests] Fix console test fail on AppVeyor
Curses is not available on Windows so skip AppVeyor test that errors
with:

    E       NameError: global name 'curses' is not defined
2019-05-08 21:41:28 +01:00
Calum Lind
2296906ed3 [Common] Replace platform.linux_distribution function
As of python 3.5, this function is marked as deprecated.
So, [distro][1] is the one we will use (this package is listed at the
example package in the python's [docs][2]).

[1] https://pypi.org/project/distro/
[2] https://docs.python.org/3/library/platform.html#platform.dist
2019-05-08 21:24:45 +01:00
Calum Lind
1a134cab1b [#3248|Console] Fix not accepting input under Python3
On Python 3 the chr function returns unicode so trying to decode will
result in an error. Applied a workaround to assign without decoding.
2019-05-08 21:02:09 +01:00
Calum Lind
7d67792493 [Lint] Update pre-commit linter versions
- Default to python3.
- Needed to add six to isort config.
2019-05-03 17:50:42 +01:00
Calum Lind
3c18e890e8 [Tests] Fix AttributeError in test_core with Twisted 19
Should be setting header contents to string not ints and latest version
of Twisted raised an error encountering int. Also correct the header
name for setting length.
2019-05-03 15:35:32 +01:00
Calum Lind
615500e6e6 [Plugins] Fix missing deregister for JSON 2019-05-03 14:57:30 +01:00
Calum Lind
1425fe5413 [Tox] Pin pip version to fix PEP517 issues
* Using pyproject.toml for black config pip version 19.1 errors out
   about using editable install with pyproject.toml.
   Workaround is to not use pip 19.1 in tox.
 * Pin to 18.1 to avoid pip-wheel-metadata-folder creation

Ref:
 - https://github.com/pypa/pip/issues/6434
 - https://github.com/pypa/pip/issues/6213
2019-05-03 14:53:34 +01:00
Calum Lind
84643fb6f7 [GTK] Remove running reactor in Gdk thread
The Gdk threading code is causing issue on Windows and this method
of moving the main loop to a thread has been deprecated and advised
against so removing without adding replacement as it currently
only creates one main thread and should still be fine.

If a blocking operation occures and needs solving, see the pygobject
guide for recommened way to use threads in Gtk:

https://pygobject.readthedocs.io/en/latest/guide/threading.html
2019-03-29 15:13:02 +00:00
Calum Lind
c8b621172e [Lint] Fix flake8 3.7 warnings
- Fix new flake8 warnings from latest version.
  Note: The `addSlash` variable was orphaned with no reference in
        Twisted or Deluge code so removed.

- Update pre-commit config
  - New pinned versions.
  - Fix prettier output.
  - Use new flake8 hook config and add naming plugin.
2019-03-29 14:27:30 +00:00
Calum Lind
02e07dda2a [Docs] Fix recommonmark monkey patch and pin version
The refactored patch did not work so revert it.

A new release of recommonmark breaks the docs build so pin it to working version.
2019-03-29 14:27:18 +00:00
Calum Lind
b2e19561e6 [GTK] Fix file manager window popup behind Deluge
Added 'TIMESTAMP' key to startup-id string for dbus method. Unsure if
this is the correct way to specify startup id but it seems to work.

Recreate the dbus session with each call since if there is an error
with the dbus method then it will crash and subsequent calls will fail
with a cryptic message:

   dbus error the name was not provided by any .service files
2018-11-17 14:36:38 +00:00
Calum Lind
389f4167b2 [i18n] Fix incorrect GB translation 2018-11-17 12:17:30 +00:00
Calum Lind
63cc745f5b [Core|Py3] Fix fastresume data not being loaded
Decode the resume_data dict keys to fix lookups with torrent_id strings
not finding resume_data.
2018-11-17 12:12:55 +00:00
Calum Lind
1a4ac93fbb [Lint] Bump prettier version to 1.15.2 2018-11-16 15:06:30 +00:00
Calum Lind
582f60ea0b [Tests] Fix failing tracker_icons test
Disable the testing with seo.com as the site certificate has expired.

Ideally should not be testing against live sites and instead use request
replay tool such as VCR.py.
2018-11-16 15:06:30 +00:00
Calum Lind
157f6ff62a [WebUI] Catch unhandled 'Bad host id' exception
The bad host id error usually occurs on webui when the 'default_daemon'
key in web.conf does not exist in hostlist.conf.

Added a errback method to output a more useful log message.
2018-11-16 15:06:30 +00:00
Calum Lind
bf4244e8b2 [GTK] Fix adding non-ascii torrents and paths on Py2
Added decode_bytes to all widgets returning text to ensure unicode on
Python 2.
2018-11-16 15:06:30 +00:00
Calum Lind
25cfd58792 [GTK] Refactor deluge.common usage 2018-11-16 15:06:30 +00:00
Calum Lind
09d04aaac0 [Core] Fix showing incorrect file priorities
Removed previous workaround to ensure sync of file priorities with
libtorrent. This did not work when loading torrents as the status is
called before setting the file priorites and resets them to default.

Removed the call to set_file_priorities when writing the torrent file
to disk as it resets the options to default so although the torrent
file priorities do not change, the priorities for UIs is incorrect.
2018-11-16 15:06:30 +00:00
Calum Lind
27b4e2d891 [Docs] Fix formatting of exported docstrings 2018-11-16 15:06:30 +00:00
Andrew Resch
043344b986 Modify the transfer protocol in a couple ways.
Replace the 'D' header with an unsigned byte that indicates the protocol version. This will allow easier changes to protocol in the future.
Replace the signed integer used for message length with an unsigned 32-bit integer. There is no need for a signed value here as a message length must always be positive. This also doubles the max message length.
2018-11-12 19:44:00 -08:00
Calum Lind
3b8f71613b [Packaging] Fix deps for win32
- Fixed trying to install py2-ipaddress breaking on Python3.
- Add wheel universal option so Py2 and Py3 wheel built.
2018-11-12 10:10:41 +00:00
Calum Lind
10fcbecc04 [Common] Fix win32 set env issue on Python 3
- On Python 3 find_msvcrt returns None and _wputenv should be used with
unicode strings.
- Removed the alternative msvcrt set env since `cdll.msvcrt` should suffice.
- Removed the broad exception catching.
2018-11-12 10:05:45 +00:00
Calum Lind
ab7f19fbb8 [GTK] Fix no torrent selected on startup 2018-11-09 10:41:08 +00:00
Calum Lind
b665a4a6f7 [#3211|Packaging] Fix missing tray icon
Fixed not including deluge-panel.png in the packaging install.
2018-11-09 10:40:03 +00:00
Calum Lind
2c45e59900 Fix UnicodeDecodeErrors with files containing non-ascii chars
The main issue here is a user trying to start deluge and the XDG
`user-dirs.dirs` file contains dir names with non-ascii chars causing
a UnicodeDecodeError when comparing with unicode chars since Py2
default encoding is ascii.

The solution is to use io.open as used elsewhere in code with
encoding set to utf8. Applied to all usage of open in common.
2018-11-08 22:37:26 +00:00
Calum Lind
89868cc944 [GTK] Fix needing bytes with hashlib on Py3 2018-11-07 15:52:26 +00:00
Calum Lind
841cb889aa [Execute] Fix Glade layout and Py3 bytes issue 2018-11-07 15:52:26 +00:00
Calum Lind
6b2f14e51e [GTK] Fix windows not showing topmost on desktop
When showing the main_window, Add dialog or file manager windows they
would not appear at the top of the display stack, always one below.

This is due to needing the windowing timestamp to be passed when making
these calls. The recommended Gtk solution to use present_with_time and
use an event.time timestamp. However, this does not always work so
instead used the lower level Gdk set_user_time and fetch timestamp from
X11 server.

Notes:
- Using int(time.time()) for timestamp is not correct as the
  windowing timestamp is different.
- Gtk.get_current_event_time only works when there is an event being
  processed.
- It might be useful for non-X11 windowing systems to store event
  timestamps so that we have a value to use instead of 0.
2018-11-07 15:52:26 +00:00
Calum Lind
7e2192e875 [GTK] Fix showing sidebar and tabsbar
- Fixed the sidebar position not being restored by applying the config
  value in main_window first_show and updating config in position
  callback.
- Renamed the main_window vpaned and hpaned widgets to aid identifying
  purpose.
- Fixed filtertreeview KeyError when not conneted and hiding tabsbar.
- Fixed the tabsbar notebook not being hidden on restart by adding a
  new config value.
2018-11-07 15:52:26 +00:00
Calum Lind
f11a42b9bf [GTK] Remove old builtin notification config values 2018-11-06 18:15:23 +00:00
Calum Lind
845204178b [AutoAdd] Fix GTK3 AttributeErrors 2018-11-06 14:57:55 +00:00
Calum Lind
d937a323fb [GTK] Replace all deprecated VBox and HBox
Use the recommended Gtk.Box.new() signature to match the GTK usage.
2018-11-06 14:57:55 +00:00
Calum Lind
d7c48d27d8 [Label] Fix mnemonic labels
Remove the icons to simplify code since ImageMenuItem is deprecated.
2018-11-06 14:57:23 +00:00
Calum Lind
1bc766213c Remove stray debug logging lines 2018-11-06 14:57:12 +00:00
Calum Lind
775aef5f9b [WebUI] Fix creating icon on GTK3 prefs page 2018-11-06 11:38:21 +00:00
Calum Lind
83cac4978a [GTK] Fix and cleanup storing window position 2018-11-06 11:19:00 +00:00
Calum Lind
2bb9a8e71c [GTK] Fix tray preferences greyed out
When not connected the tray preferences should still be available.

There is no need to have the `is_connected` applied to widget
sensitivity here as that is set elsewhere in the code.
2018-11-06 11:16:20 +00:00
Calum Lind
39783c7703 [GTK] Fix systray popup TypeError
An incorrect number of arguments supplied to GtkMenu.popup
2018-11-06 10:47:17 +00:00
Calum Lind
9f9f564e62 [GTK] Fix unicode warnings on Python 2
GTK3 on Python 2 returns bytes so decode before comparisons.
2018-11-05 16:47:58 +00:00
Calum Lind
ab1b2bcf14 [Exceute] Fix GTK3 pack_start missing paramters 2018-11-05 16:47:06 +00:00
Calum Lind
bb0c61bb3f [GTK3] Replace deprecated set_data with attribute assignment 2018-11-05 08:38:54 +00:00
Calum Lind
a7dcf39a32 [GTK3] Fix pathcombo getting folder name on wrong widget 2018-11-05 08:26:23 +00:00
Calum Lind
e43796ae51 [GTK] Fix missing sidebar tracker icons
The filename for tracker_icons is an absolute path so check the
path before calling get_pixmap which is for relative ui/data paths.
2018-11-05 08:26:23 +00:00
Calum Lind
6655fe67c3 [UI|Core] Fix problems with file priorities
- Fixed the core not correctly settings the current file_priority
settings and added a test.
- Fixed the console not setting file priorities.
- Change the label for not downloading of a file to 'Skip'.
2018-11-05 08:26:23 +00:00
Calum Lind
2104b9831c [Core] Fix file_renamed alert returning method
Fixed a typo that resulted in the new_name method being emitted
instead of the string.
2018-11-02 09:00:42 +00:00
Calum Lind
e7127637cf [Dependency] Remove bundled rencode 2018-11-02 08:47:57 +00:00
Calum Lind
6233e5c844 [Blocklist] Fix detecting compression type on Py3
The magic number is in bytes so ensure bytes in COMPRESSION_TYPES
2018-11-02 08:47:57 +00:00
Calum Lind
a01481b26f [Plugins] Update create script and add GTK3 how-to doc
- Updated create_plugin script to create a GTK3 plugin.
- Added a document for updating a 1.3 plugin to be compatible with
  2.0.
2018-11-02 08:47:57 +00:00
Calum Lind
3d24998577 [Docs] Fix duplicate description warnings 2018-11-02 08:47:57 +00:00
Calum Lind
f24e9d152c [Common] Remove oldest archive if too many exist
Prevents the archive folder bloating.
2018-11-02 08:47:57 +00:00
Calum Lind
f47089ae7d [Core] Archive corrupt torrent.state on load
If the torrent.state was corrupted then loading would create a new
state with no backup to examine.

The solution is to use the archive function to save a copy of the
torrent.state.

Added a message argument to archive_files so that the error message
with a reason for archiving can be included in the tarball.
2018-11-02 08:47:57 +00:00
Calum Lind
d70abd2986 [Plugins] Refactor plugin scan code
Simplify adding entries to the working_set.
Also fixes adding files rather than just dirs to working_set.
2018-11-02 08:47:57 +00:00
Calum Lind
7d998a45f2 Shorten code to declare namespace
This is the recommened way of declaring namespace.

Also remove unneeded unicode_literals import.
2018-11-02 08:47:57 +00:00
Calum Lind
3433a911cc [Plugins] Allow enabling any plugin Python version
Users encounter issues when trying to install plugins with differing
python versions. If the plugin was built with Py2.6 but they are using
Py2.7 the plugin would not load. With the move to Python 3 this could
become more of an issue. The workaround is to let the plugin manager
to try to load the deluge plugin regardless of the python version it
was built with.

This will put the onus on plugin author to keep the plugin code
compatible with more Python versions.
2018-11-02 08:47:57 +00:00
Calum Lind
967606fa0f [Tests] Fix str/bytes issue on Python 3
- argparser does not accept bytes and raised an error with encoded vars.
2018-11-02 08:47:57 +00:00
Calum Lind
97e7d95dd3 Cleanup tox configuration
There were issues with dependencies and tox environments under Python 3
so refactored the tox configuration to be more consistent and clearer.

- Moved travis to default to Python 3 for linting and tests.
- Fixed missing mock for cairo in sphinx config.
- Collated the base deps sections to improve readability.
- Added PYTEST_ADDOPTS env to override pytest verbosity in just tox
  tests as this was a common option being used.
- Renamed env 'testcoverage' to the more concise 'coverage' and moved
  html creation under single env as handy to have this output as well
  as report.
- Cleaned up the isort config for gtk3.
- Added `bad-continuation` to pylint config as conflcts with black
  formatting.
- Fix isort issue with bbfreeze script. This will likely be removed
  in future so just skip sorting it.
2018-11-02 08:47:57 +00:00
Calum Lind
26c28445a5 [GTK3] Fix showing piecesbar 2018-11-02 08:45:40 +00:00
Calum Lind
74a459274c [GTK3] Cleanup widget placement and spacing 2018-11-02 08:45:40 +00:00
Calum Lind
bb6e290bf8 [GTK3] Fix UnicodeWarning in row comparison on Python 2 2018-11-02 08:45:40 +00:00
Calum Lind
4a79e1f100 [GTK3] Save ui files with Glade 3.22
To ensure properties are updated this is a simple open and save with the
glade designer. Always a bit messy with the diff but should not
change functionality.
2018-11-02 08:45:40 +00:00
Calum Lind
bff93bb162 [Appveyor] Remove PyGTK and disable win32 packaging 2018-11-02 08:45:40 +00:00
Calum Lind
bffd091429 Update DEPENDS for GTK3 2018-11-02 08:45:40 +00:00
Calum Lind
70d5931622 [GTK3] Fix column header right-click menu popup
The popup_at_pointer method is only available in GTK >=3.22 so for
compatibility restore using popup method.

Right-clicking on column headers popped-up torrent menu so only show
this menu when in torrentview.
2018-11-02 08:45:39 +00:00
Calum Lind
ce49cde49d [GTK3] Fix cmp sorting on Python 3 2018-11-02 08:45:39 +00:00
Calum Lind
a3bd2e547a [UI] Use Pillow for .ico icons
Pillow added the code in Win32IconImagePlugin to v2.1 (in 2013) so we can remove it.
Refactored the tracker_icons code to reflect this change.
2018-11-02 08:45:39 +00:00
Calum Lind
64710ad226 [GTK3] Disconnect after editing host in connection manager 2018-11-02 08:45:39 +00:00
Calum Lind
cd6bad0e35 [UI] Fix passing bytes config path to subprocess on Python 3
The % substitution was causing the bytes prefix to become part of the
string and created a `b'/` prefixed config directory. Ensure the config
arg is byte prefixed too.
2018-11-02 08:45:39 +00:00
Calum Lind
1310645f55 [GTK3] Translate daemon status in connection manager
- Added a tooltip to show text status which also required translation
  so created a new liststore column for the translated text to ensure
  status is parsed correctly.
- Forced the status to lowercase to avoid translation issues and
  simplify comparisons.
2018-11-02 08:45:39 +00:00
Calum Lind
e6a7119595 [GTK3] Replace stock icon and text usage 2018-11-02 08:45:39 +00:00
Calum Lind
0b39b529dd [PY3] Fix tray password encoding issue
The tray password need to be in bytes but GTK on Py3 returns unicode.

Use decode_bytes and then encode to ensure Py2/3 compatibility.
2018-11-02 08:45:39 +00:00
Calum Lind
1d0e40c66b fix move_completed sensitivity 2018-11-02 08:45:39 +00:00
Calum Lind
bcc89c73dd [GTK3] Fix statusbar clicking issues 2018-11-02 08:45:39 +00:00
kbdserver
a6b47e18c9 Fix package_data namespace in setup.py 2018-11-02 08:45:39 +00:00
Calum Lind
5183c92543 [GTK3] Migrate to AppIndicator3
Replace the old appindicator imports with AppIndicator3.

- Only few changes required due to Enum renaming.
- Updated the preference import to include require_version and set a bool.
- The password preference needs to be encoded for hashlib on Python3 but
also need to keep Python 2 support so attempt decode then encode.
2018-11-02 08:45:39 +00:00
Calum Lind
7c1c3f62d1 [Tests] Ensure GTKUI tests are skipped upon import errors
Need to catch any issues with importing GTK modules to ensure
GTKUI tests are skipped in non-GTK environments.
2018-11-02 08:45:39 +00:00
Calum Lind
729f062ea1 [GTK3] Fix RadioMenuItem group error
Fixes a difference from GTK2->3 where the group can no longer be passed
as a RadioMenuItem so use get_group method to set group.
2018-11-02 08:45:39 +00:00
kbdserver
d879ee06a3 [Notifications] Migrate to GTK3
- Switch from pynotify to gi libnotify binding.
  - Ideally would drop libnotify for [GNotification] but requires more
    work with the desktop file needing renamed to DNS format.
    e.g. `org.deluge-torrent.deluge`

[GNotification]: https://developer.gnome.org/GNotification/

Co-authored-by: Calum Lind calumlind+deluge@gmail.com
2018-11-02 08:45:39 +00:00
kbdserver
ed1b2a50fa [Scheduler] Migrate plugin code to GTK3
- Added new svg icons for cleaner look.
- Use widget get_allocated_width and get_allocated_height instead of window size.
- Tweaked margins to fix spacing issues.
- Removed yellow background from 'slow settings' and applied only to label.

Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
2018-11-02 08:45:39 +00:00
Calum Lind
c51e01ac46 [GTK3] Migrate plugins to GTK3
Add a new Gtk3PluginBase to prevent problems with Gtk2 plugins.
2018-11-02 08:45:39 +00:00
kbdserver
4df5bd05ec [GTK3] Replace stock icons for named icons 2018-11-02 08:45:39 +00:00
Calum Lind
cf4012bb60 [GTK3] Fix and remove FIXME comments
- Several of the FIXME comments seem to be outdated so removed.
- The status_icon comment was resolved by fixing the arguments supplied to
tray_menu.popup().
- The TreePath no longer returns a tuple so cast path to int.
- Fix an error with Pixbuf signature.
2018-11-02 08:45:39 +00:00
Calum Lind
bbcebe1306 [Tests] Fix use get_iter_first for treestore 2018-11-02 08:45:39 +00:00
Calum Lind
bcaaeac852 [GTK3] Use explicitly named functions for creating menuitems
See https://wiki.gnome.org/Projects/PyGObject/InitializerDeprecations
2018-11-02 08:45:39 +00:00
Calum Lind
4111f94597 [GTK3] Fix GError required attr id for GtkTreeSelection 2018-11-02 08:45:39 +00:00
Calum Lind
dd7cc31918 [GTK3] Fix catching Wnck not available 2018-11-02 08:45:39 +00:00
Calum Lind
d8d094cab6 [Travis] Fix missing pygi gtk3 packages 2018-11-02 08:45:39 +00:00
Calum Lind
dc6e93541b [Docs] Fix mocking external modules
With move to GTK3 needed to update the mocking of external modules.

There is a new autodoc option `autodoc_mock_imports` so use this instead
of the custom mock class.

There are some build warnings output using autodoc mock:

    TypeError: unsupported operand type(s) for |: '_MockObject' and '_MockObject'

Will resolve these later as the build passes.
2018-11-02 08:45:39 +00:00
Calum Lind
f6ffb940ab [Tests] Update tests for GTK3 new paths 2018-11-02 08:45:39 +00:00
Calum Lind
6fbb1bb370 [GTK3] Fix ui_entry tests for gtk3 2018-11-02 08:45:39 +00:00
Calum Lind
8285b226eb [GTK3] Fix Python 3 issues
In Python 3 builtin next function instead of the next method.

Unpickling with translated strings in state file causes ascii decode
error so ensure UTF-8 encoding is specified.
2018-11-02 08:45:39 +00:00
Calum Lind
194129c027 [GTK3] Fix create torrent dialog warning
The ui liststore had an extra blank value causing a GTK value type
warning.

Also set the default value displayed to be 128 KiB.
2018-11-02 08:45:39 +00:00
Calum Lind
7d5a429466 [GTK3] Fix column lookup not i18n-ised 2018-11-02 08:45:39 +00:00
Calum Lind
ac5db1b262 [GTK3] Fix gettext translation code
Add translation setup for Gtk.Builder ui files.

Refactor and cleanup up the translations_util:
- Remove old gtk.glade code.
- Add macos libintl support.
- Remove unneeded setup_translations parameters.
2018-11-02 08:45:39 +00:00
Calum Lind
a2857a318d [GTK3] Remove unneeded code in path chooser
Not sure the reason for the added introspection code but originated in
GTK3 changes so remove as path chooser doesn't appear to need it.
2018-11-02 08:45:39 +00:00
Calum Lind
13e1fa355d [GTK3] Fix path chooser warnings and errors 2018-11-02 08:45:39 +00:00
Calum Lind
2e88fa1dfc [GTK3] Remove listview orphaned code
Not sure why/where this originated from but seems to serve no purpose so
removing it.
2018-11-02 08:45:39 +00:00
Calum Lind
366b10f07b [GTK3] Fix displaying column popup menu
Right-clicking on column header resulted in this error:

    TypeError: could not convert type EventButton to GdkEvent required for parameter 0

The following fixes and cleans up the issue:
- Move the signal creation to the class, using the __gsignals__ dict.
- Replace `Event` with `object` since we are passing an EventButton as
Gtk3 no longer accepts it as an Event.
- Replace deprecated menu `popup()` with `popup_at_pointer()` which also
fixes a critical gdk error when using `popup()`.
2018-11-02 08:45:39 +00:00
Calum Lind
92a048625a [GTK3] Fix the transient parent for PathChooser
The filechooser dialog was wrongly transient to the main window causing
weird behaviour, namely the main window moving but dialog remaining in
place when attempting to move the child dialog.

The solution is to pass the parent dialog to PathChooser so it can be
properly set the filechooser dialog transient property.

Fixed the Preferences dialog not being set to be modal to main window.
2018-11-02 08:45:39 +00:00
Calum Lind
8199928160 [GTK3] Use a non-CSD filechooser dialog for PathChooser
My personal feeling is that GTK client-side decoration (CSD) putting
main dialog buttons in the titlebar is wrong so create a non-CSD dialog.

There was no simple way of changing GtkFileChooserDialog to play nice
with non-CSD buttons and resulting in these GTK warnings:
    Gtk-WARNING : Content added to the action area of a dialog using header bars

There is an unwanted dialog border with this custom filechooser dialog
with no apparent way to remove them. Would require switching to a
GtkWindow implementation.
2018-11-02 08:45:39 +00:00
hugosenari
545aca9a4c [GTK3] Fix piecesbar Pango methods
Fixes 'Context' object has no attribute 'create_layout'
Fix the gi cairo import warning
2018-11-02 08:45:39 +00:00
hugosenari
9f113eab23 [GTK3] Fix ImportError has no attribute message
Python 3 exception objects now use `msg` but casting with `str()` is
better.
2018-11-02 08:45:39 +00:00
hugosenari
bc6bc017cb [GTK3] Fix TextBuffer.get_text() arguments
This fixes an error that it takes exactly 4 arguments (3 given)
2018-11-02 08:45:39 +00:00
hugosenari
535fda90e3 [GTK3] Use core.pause_torrents with list of torrents
Use the new api method.
2018-11-02 08:45:39 +00:00
hugosenari
0ace086de4 [GTK3] Fix About 'MainWindow' object has no attribute 'get_window' 2018-11-02 08:45:39 +00:00
hugosenari
bbb1b44a23 [GTK3] Fix piecesbar window attribute error
Fixes the error: object has no attribute window
2018-11-02 08:45:39 +00:00
hugosenari
dfed17ac0d [GTK3] Fix import and attribute warnings
Fixes:
- warning import GConf
- Gtk is not defined
- 'ResponseType' has no attribute 'Yes'
- selection.data should be selection.get_data
2018-11-02 08:45:39 +00:00
hugosenari
2f879c33f3 [GTK3] Fix path_combo_chooser warnings
Fixes
 - Please remove the widget from its existing container first
 - object has no attribute 'height'
2018-11-02 08:45:39 +00:00
hugosenari
14b6ba10cf [GTK3] Use decode_bytes from method returns
Python3 PyGObject automatically encodes/decodes strings to and from
methods. This does not happen on Python 2 so for compatibility use
decode_bytes.
2018-11-02 08:45:39 +00:00
hugosenari
ae0b072b75 [GTK3] Fix config default_load_path keyerror 2018-11-02 08:45:39 +00:00
hugosenari
250afa6e0b [GTK3][OSX] Restore windowing lookup for quartz 2018-11-02 08:45:39 +00:00
hugosenari
b29b6fe69f [GTK3] Restore and update expand_row code 2018-11-02 08:45:39 +00:00
hugosenari
f160d6312f [GTK3] Fix piecesbar warnings
Fixes:
- get_style to get_style_context
- use str with gsignals
2018-11-02 08:45:39 +00:00
hugosenari
a8d01fd52f [GTK3] Fix GObject deprecation warnings
GObject.idle_add is deprecated using GLib.idle_add instead
GObject.timeout_add is deprecated using GLib.timeout_add instead
GObject.SIGNAL_RUN_LAST is deprecated; use GObject.SignalFlags.RUN_LAST instead
GObject.GError is deprecated; use GLib.GError instead
GObject.timeout_add is deprecated use GLib.timeout_add instead.
ListStore(str, str) using unicode_literals get_value return utf8.
2018-11-02 08:45:39 +00:00
hugosenari
3a5ec4f5f4 [GTK3] Fix Glade ui margins and spacing 2018-11-02 08:45:39 +00:00
hugosenari
eebb93d4ee [GTK3] Cleanup Glade UI deprecated widgets and properties
- VBox is Box(vertical);
- HBox is Box;
- HButtonBox is ButtonBox;
- HSeparator is Separator;
- VPaned is Paned(vertical);
- Table is Grid;
- use_action_appearance is purged;
- yalign is purged;
- xalign is purged;
- xoptions are purged;
- yoptions are purged;
- add some align: start;
2018-11-02 08:45:39 +00:00
Calum Lind
01fafd4fe0 [GTK3] Change module structure from ui/gtkui to ui/gtk3
This moves the directory structure so that there is no conflict with the
old gtk2 UI. Also changes the conf and state files being loaded.
2018-11-02 08:45:39 +00:00
Calum Lind
ca0db4d1a7 [GTK3] Save ui files with Glade 3 2018-11-02 08:45:39 +00:00
Calum Lind
ea72164798 [GTK3] Large rename/modify code for GTK3 2018-11-02 08:45:39 +00:00
Calum Lind
e2ba980299 [GTK3] Marked FIXME code changes for GTK3 2018-11-02 08:45:39 +00:00
Calum Lind
98051bdea2 [Docs] Move apidoc command to Sphinx config
The apidoc modules were not being generated on ReadTheDocs because
there is no way to run sphinx-apidoc manually.

Moved the running of sphinx-apidoc into conf.py.

Added zope.interface minimum version to fix Readthedocs warning.
2018-11-02 00:21:50 +00:00
Calum Lind
20431cc771 [Docs] Fix build errors getting Deluge version
The use of pkg_resource.require caused an unwanted requirements lookup
that errored out the sphinx build when no dependencies are installed.

This is fixed by switching to pkg_resources.get_distribution.

Also changed the tox docs env to not install Deluge as the setup.py
now contains install_requires which is unwanted.
2018-11-01 23:18:05 +00:00
Calum Lind
82ecf8a416 [Docs] Reorganise and add sections from wiki
- Change the layout and contents of docs to be better organised and
  follow ideas from: https://www.divio.com/blog/documentation/
- Use markdown for non-technical documents to speed up writing.
- Added new sections and imported documents from Trac wiki.

Build fixes:

- Added a patch to fix recommonmark 0.4 and doc referencing:
    https://github.com/rtfd/recommonmark/issues/93
- Set docs build in tox to Py2.7 since there are problems with autodoc
  mocking multiple inheritance on Python 3 resulting in metaclass errors.
- Supressed warning about `modules.rst` not in the toctree by creating
  a static `modules.rst` with `:orphan:` file directive and add to git.
  Also skip creating this toc file with sphinx-apidoc in setup and tox.
- Simplified finding exported RPC and JSON API methods by adding an
  autodoc custom class directive. Removed unneeded __rpcapi.py.
2018-11-01 17:38:10 +00:00
Calum Lind
9dcd90056d [Lint] Fix flake8 warnings
- Use six to silence flake8 undefined Python 2 names on Python 3.
- Fix W605 invalid escape sequence.
- Cleanup unused exception variables.
2018-10-25 15:14:19 +01:00
Calum Lind
e2c7716ce2 [#1903|UI] Add super seeding option to interfaces
- Fix applying the setting to libtorrent, passing the value without
  modification so it decide when to enable it.
- Enable super_seeding option when adding torrents to core.
- Update UIs with option in tabs and add dialogs.
2018-10-25 15:14:07 +01:00
Calum Lind
e6c61c3f8c [Core] Fix potential renaming unicode folders issue
- Found an issue while fixing `get_name` where `handle.rename_file`
  would raise a UnicodeDecodeError with non-ascii on Python 2. The
  fix is to catch this and pass unicode string to method instead.

- Add a test `test_rename_unicode` to verify no errors are generated.
- Updated test to use core.session instead of creating another one.
2018-10-22 21:58:06 +01:00
Calum Lind
b834e33568 [#3204|Core] Fix unicode get_name unicode error
The recent change to torrent.get_name does not handle non-ascii paths
on Python 2.

- Add a decode_bytes to resolve the issue.
- Add tests.
- Refactor to reduce nesting.
2018-10-22 21:58:05 +01:00
Calum Lind
9ab2a50097 [Console] Refactor single letter variables
- Replace usage of `s` for variable names to make it easier to read the code.
- Remove unneeded and unused encoding parameter from parse_color_string
  It should not be encoded by this function, only on output.
2018-10-22 16:05:55 +01:00
Calum Lind
1838403e3b [#3199|Console] Fix UnicodeEncodeError in addstr
The following error was encountered by user:

   ...deluge/ui/console/modes/basemode.py, line 290, in add_string
       screen.addstr(row, col, string, color)
   UnicodeEncodeError: 'ascii' codec can't encode character...

The `add_str` method is defaulting to using the Python 2 ascii
encoding with a unicode string so use the encoding passed to the
function.
2018-10-22 16:00:12 +01:00
Calum Lind
9e7c9fc1d3 [AutoAdd] Fix packaging autoadd_options data files
There was a warning about missing javascript file in the package and
found the `data/autoadd_options` subdir was not being included.

Bump version
2018-10-21 16:05:01 +01:00
DjLegolas
9264cb749e [WebUI][#2009] Add About window
- Add an About window to see version details like GTKUI.
- The author and license text were left out as unnecessary.
- Added a daemon get_version method since daemon version was not
  available through the json-api.
- Fix LookupResource to ensure path exists when rendering.
2018-10-21 15:43:29 +01:00
Phil Hudson
c01679de1f [Config] Prevent symlinked config files being overwritten
If a user keeps the deluge config file under source control and symlinks
to the config files in deluge config dir then when deluge saves config
files it will replace the symlink with actual file.

Using realpath will resolve these symlinks and file will be updated in
the correct location.

Use a temporary file for new config new before moving it to the
resolved location.

Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
2018-10-21 14:08:30 +01:00
Calum Lind
3645feb486 [GTK] Fixup translation strings
- Use named placeholders to allow translators to change text order.
- Refactor fpcnt to use format() and markup strings properly.

- Remove gettext.js creatation from generate_pot script since this step
  is now always done at build-time.
2018-10-21 13:51:30 +01:00
Calum Lind
d85f665091 Fix large ETA overflow C int
The following error was encountered in GTK3 which is a result of trying
to cast a very large ETA value to C int and raising an Overlflow error.

   <type 'exceptions.OverflowError'>: 3072227291 not in range -2147483648 to 2147483647

The solution is to limit the ETA to 1 year and represent any values over
that as -1 which the UIs can display as infinity.
2018-10-19 17:30:27 +01:00
Calum Lind
5ec6ae3ad0 [Packaging] Minimal requirements for test_requires in setup.py
Remove extra_requires since requirements-*.txt files provides these now
plus the extras_requires is for extras at installation time which does
not apply to docs or dev.

For test_requires include the minimal requirements for pytest to run.
This is not the same as the longer tox test requirements that include
linting, docs etcs.

Fix license field.
2018-10-19 14:14:03 +01:00
Calum Lind
860730d43c [Packaging] Update make_release script for Py3
xz compression is included in Python 3 so simplify script.
2018-10-19 14:14:03 +01:00
Calum Lind
c1ddcf6012 [Packaging] Add install_requires to setup.py
- Add an install_requires list to allow dependencies to be automatically
  installed via setuptools or pip installation.
- Needed a workaround for twisted service_identity install.
2018-10-19 14:14:03 +01:00
Calum Lind
85bbdfe143 [Packaging] Cleanup dependencies
- Tweaked the layout a bit with optional part of dependency description.
- Updated descriptions to help understand dependency usage.
- Made intltool and chardet packages optional. This will help with
  installation where these might be missing and are not crucial.
- Remove gettext from dependency as is part of Python.
2018-10-19 14:14:03 +01:00
Calum Lind
9f9827ca58 [Packaging] Fix appdata.xml details
- License should be GPL3+.
- Use the Deluge HTTPS URLs.
- Wrap the description long lines.
- Add launchable tag to launch from software centre after install.
- Removed name tag to use .desktop file name field instead.
2018-10-19 14:14:03 +01:00
Calum Lind
dcb3dad435 [Core] Fix renaming folder not updating torrent name
The new get_name method needs to function as it did in 1.3-stable so
when renaming a torrent file or top-level folder update torrent name
to reflect that. If UI supports renaming the torrent then the options
value with be used instead.
2018-10-19 14:02:36 +01:00
Calum Lind
0e69b9199c [GTK] Fix toggling auto_managed in Options tab
The refactoring of the options code didn't update to use the new status
key `auto_managed` and `is_auto_managed` is not a valid torrent option.

Related: 1637da84e4
2018-10-19 14:02:22 +01:00
Calum Lind
88a3600ce3 Fix sdist missing test files
The exclude 'deluge.tests' in setup.py is for excluding from package
installation but we want the files when distributing as source so
include the entire deluge/tests dir (excluding .pyc files).
2018-10-16 14:59:00 +01:00
Calum Lind
91164d8dbf Cleanup and use markdown for source text files
Use markdown to aid readability.

Update the README and use it for the long_description in setup.py
Add detailed requirement information to the DEPENDS files.
2018-10-16 14:59:00 +01:00
Calum Lind
ec47720686 [WebUI] Enable debug URL parameter to parse false values
When installed as a development version there was no way to load the
normal js scripts so improve the debug arg handling by parsing for false
values to force use of normal type scripts. Since debug arg overrides dev,
leave dev as is.
2018-10-16 11:52:04 +01:00
Calum Lind
467ade1eb7 [WebUI] Fix closure minified size increase
All of the non-standard docstring file headers were being added to
minified files, increasing the file size. Replace with jsdoc `/**`.

Remove ext-extensions from git as will be generated by minify script.
2018-10-16 11:38:13 +01:00
Calum Lind
bb93a06fff [WebUI] Fix prettier javascript issue
Prettier removed too many parentheses in asIPAdress and when minifying
the plus unary combined with the plus operator to create an incorrect
increment operator. So skip prettier formatting this secion of code.

Also fix an ECMA5 warning from closure about function scope in FilesTab.
2018-10-16 11:37:11 +01:00
Calum Lind
80178f7310 Update javascript minifying script
- When both minifying modules are missing, creating a copy of the debug
  file is not actually desirable, a missing file is more obvious than a copy.
  WebUI can handle a missing 'normal' script and fallback to 'debug' script so
  modified script to skip and warn instead.
2018-10-16 11:34:55 +01:00
Calum Lind
ee354eb107 [WebUI] Keep debug js in packaging and fix script lookup
Packaging:

- Decided that the debug files are useful for end-user so keep them in
  package installation. For debug script_type to be usable all debug
  file need to be avaialble so extjs debug files also included.

Script type selection:

- Fixed dev and debug request args to be properly decoded on Python 3,
  otherwise comparison would fail and allowed any case for values.

- Modified the choosing of the script type to pick debug if specified
  as previously always choosing dev type if dev version was True. A rare
  scenario but useful but now debug is used if specified otherwise use dev.

- Changed the order when looking for alternative script types to start
  with dev so that if debug is specified but missing it uses a similar
  script type as previously would fallback to normal which is likely
  undesired.
2018-10-16 11:29:41 +01:00
kbdserver
7d896599b8 [GTK] Fix speed appearing in blanks cells
In TorrentView and PeersTabView, when moving the mouse pointer through
rows, the value in download and upload speed columns can be overwritten
by value from previous row cell.

The solution is to disable return by cached condition in function
cell_data_speed.

Discussion: https://github.com/deluge-torrent/deluge/pull/200#issuecomment-424907571

Removed debugging code.

Co-authored-by: Calum Lind <calumlind+deluge@gmail.com>
2018-10-14 10:52:08 +01:00
Calum Lind
55aee2b00f [Common] Fix incorrect path in TorrentInfo
Refactoring for Python 3 did not account for the `self._files` using an
updated info_dict so simplify code by updating in the files for loop.

Added test that TorrentInfo.files returns the correct structure.
2018-10-14 10:51:08 +01:00
Calum Lind
10d39c83cb [WebUI] Fix error escaping translated text
In a previous commit d4023e7dde removed a decode for Python 3 but
with translated text returned by gettext encoded on Python 2 the
escape function would raise a UnicodeDecodeError trying to use ascii
to decode.

The fix is to decode the message returned by gettext on Python 2.
2018-10-13 22:57:28 +01:00
Calum Lind
0b2cb7539f Cleanup Tox and CI configs
- Use the apt addon for installing libtorrent package.
- Start the py3 test sooner as it is slow to complete.
- Add if conditions for gtkui test dependencies.
- Remove Appveyor tests that are taken care of by Travis.
2018-10-10 18:50:41 +01:00
Calum Lind
6fdbf0ba5d Update tox and CI for Python 3 2018-10-10 17:57:02 +01:00
Calum Lind
a980f8e959 [WebUI] Allow multiple torrent uploads in Add dialog
Add a new `multiple` field to FileUploadField to allow selecting
multiple files. Include a fallback for if browser does not support
multiple file selection.

Update Add window to upload and parse multiple torrent files at once.
2018-10-10 17:57:02 +01:00
Calum Lind
c90cf301df [WebUI] Use application/json in header 2018-10-10 17:57:02 +01:00
Calum Lind
6f06cd5ebc [WebUI|Py3] Refactor content_type check
Simplify getting content_type from request to prevent str/bytes mixup.
2018-10-10 17:57:02 +01:00
Calum Lind
86de5657ff [WebUI|Py3] Fix and refactor torrent upload 2018-10-10 17:57:02 +01:00
Calum Lind
4a335eeb61 [Tests] json loads Python 3.5 compatible
json.loads in Python 3.6 accepts str or bytes but Python 3.5 is str
only so decode.
2018-10-10 14:41:10 +01:00
Calum Lind
86d582d52a Remove debugging log line 2018-10-10 14:41:10 +01:00
Calum Lind
673b6653a3 [Py3] Fix TorrentInfo info_dict decoding 2018-10-10 14:41:10 +01:00
Calum Lind
41732fe38b [WebUI|Tests] Fix json_api tests for Python3 2018-10-10 14:41:10 +01:00
Calum Lind
5964bcf897 [Tests|Py3] Fix prefetch metdata test 2018-10-10 14:41:10 +01:00
Calum Lind
3ed4a6e834 [WebUI] Fixes for login auth on Python 3
Remove obsolete password check code.
2018-10-10 14:41:10 +01:00
Calum Lind
20fa106b8b Update pre-commit config
The prettier hook was missing a trailing slash so omitting css files.

Add a trailing space fix hook and fix issues.
2018-10-08 14:49:36 +01:00
Calum Lind
654e2af4e5 [WebUI] Fix browser Flash plugin warning
Do not perform flash player version detection using the fix from here:
  https://github.com/georchestra/georchestra/issues/902
2018-10-08 14:47:27 +01:00
Calum Lind
d5dea44689 [WebUI] Update extjs to 3.4.1.1
Better late than never...

http://cdn.sencha.com/ext/commercial/3.4.1.1/release-notes.html
2018-10-08 14:47:27 +01:00
Calum Lind
5743382c65 Remove Pipfile and pipenv
For now using requirements files and tox to setup a dev env so to
prevent confusion or stagnation of Pipfile remove it and pipenv
documentation.
2018-10-08 12:21:00 +01:00
Calum Lind
39f37e6133 [Tests] Remove slimit dependency
The changes to the minify script mean we no longer require slimit.
2018-10-08 12:19:03 +01:00
DjLegolas
0ed3554f95 [WebUI] Copy non-minified JS file if slimit missing
This will remove the setup dependency in "slimit" package.
In case "slimit" is missing, the non-minified JS files will be copied
as is to the build.
"slimit" is marked as a dependency for development process only.
2018-10-08 12:13:21 +01:00
Kirill Romanov
ba6af99b05 [Notifications] Set notification desktop entry hint
Due to Gnome Guidelines https://wiki.gnome.org/Initiatives/GnomeGoals/NotificationSource
2018-10-08 12:11:07 +01:00
Calum Lind
9e29fe4111 [Tests] Lint with pre-commit
- Add lint section to tox.
- Replace flake8 with lint on Travis and remove commented out sections.
- Remove flake8 from appveyor to reduce sequential testing time.
2018-10-05 18:45:37 +01:00
Calum Lind
a8a4fb69c0 [Lint] Exclude js and css from EOF fixer
- When running pre-commit on all files it is picking up minified js and
  css files. Since prettier will format correctly the source files ignore
  them in end-of-file fixer.
- The template files in web docs can be ignored too.
- Removed the unneeded `pre-commit-hooks` dependency as pre-commit
  resolves that itself.
- Include files fixed by pre-commit.
2018-10-05 18:45:37 +01:00
Calum Lind
6cf13d112b [Tests] Remove debug traceback 2018-10-05 13:23:45 +01:00
Calum Lind
6973f96f8c [Tests] Update tox and CI configs
- Add new requirements files to make it easier to install deps.

- Tox changes
  - Update tox to use new requirements files.
  - Tweak heading styles.
  - Add development environment command `devenv`.
  - Remove testenv command as it would run on devenv creation.

- Travis changes
  - Now uses xenial as trusty is very old now.
  - Trial run disabled to speed up tests.
  - Add tox-venv for Python 3 support.
  - Only install testssl if running security tests.

- Appveyor
  - Add tox-venv for Python 3 support.
  - Use requirements file for non-tox.
  - Remove trial run to speed up testing.
2018-10-05 13:23:45 +01:00
Calum Lind
0548bdb655 [Lint] Add pre-commit config
- Added a pre-commit config for code linting and formatting. It will
  auto-format python, javascript, CSS, YAML and markdown files to save
  manually doing so. To install:

      pip install pre-commit
      pre-commit install

- Added a default virtual environment directory to gitignore.
2018-10-05 09:45:42 +01:00
Calum Lind
36606fc448 [Docs] Add markdown support
- Use recommonmark to enable use of markdown files in docs.
- Fix theme not specified
- Remove unused spelling module.
- Cleanup mocking modules in conf so building docs requires only Sphinx.
- Simplify tox section, including use of requirements-docs file. Added
  slimit dependency for sdist-ing deluge package.
2018-10-04 15:53:42 +01:00
Calum Lind
c415b097fe Cleanup outgoing_interface code and help text
- Remove is_ip check as libtorrent does accept IP address for this setting.
  See: https://github.com/arvidn/libtorrent/issues/3087
- Use consistent wording for help text.
2018-10-04 10:51:20 +01:00
Calum Lind
970fad7557 [GTK] Fix column name missing translation markup
With non-English languages this lookup would fail without gettext
translation of the column name.

A better solution is to not use the translatable column title as an
index but this is a quick fix for now.
2018-10-04 10:39:48 +01:00
Calum Lind
358ff74d0e [Lint] Format files with Prettier
Use Prettier to auto-format javascript, CSS and YAML files so that less
manual work is involved and style is consistent across project.
2018-10-03 18:16:09 +01:00
Calum Lind
b1cdc32f73 [Lint] Use Black to auto-format code
The move to using auto-formatter makes it easier to read, submit and
speeds up development time. https://github.com/ambv/black/

Although I would prefer 79 chars, the default line length of 88 chars
used by black suffices. The flake8 line length remains at 120 chars
since black does not touch comments or docstrings and this will require
another round of fixes.

The only black setting that is not standard is the use of double-quotes
for strings so disabled any formatting of these. Note however that
flake8 will still flag usage of double-quotes. I may change my mind on
double vs single quotes but for now leave them.

A new pyproject.toml file has been created for black configuration.
2018-10-03 15:21:53 +01:00
Calum Lind
bcca07443c [Common] Fix config missing value assignment 2018-09-30 14:58:11 +01:00
Calum Lind
67d9c2efb4 [Core] Fix saving listen_interface as None
A mistake in refactoring meant that listen_interface was reset to None
on shutdown.
2018-09-30 14:58:03 +01:00
Calum Lind
34b0fdff1d [Core] Retain magnet details when loading state
It is useful to keep the magnet uri even with the torrent_info. When
adding the torrent magnet details are only used if torrent_info is not
available.
2018-09-28 15:01:34 +01:00
Calum Lind
f93e5e60b5 [Core] Refactor session status code
Simplify the methods by initialising the session_status dict using
libtorrent session_stats_metrics and rate keys.

Instead of first looking for deprecated keys use exception then lookup.

Added a few more tests.
2018-09-28 15:01:34 +01:00
Calum Lind
d8b1e2701c [#3080] Fix torrent reappearing on restart
If a magnet is added to new Deluge state, then deleted, it will reappear
on restart.

The problem results from torrents requiring both state and torrent file
but magnet only rely on the state file and the save_state code not
saving if the torrent list is empty. So torrents won't be loaded as
their torrent files have been deleted but magnets details remain in
state file and are loaded again on restart.

The fix is to always save the state file even if the state is empty.
2018-09-27 10:56:58 +01:00
Calum Lind
abf4c345f0 [#2398|GTK] Update prefetching magnet metadata in AddTorrent dialog
The new code automatically attemps to fetch magnet file details from
core while displaying a 'waiting' message.
2018-09-26 14:18:52 +01:00
Calum Lind
a09334e116 [GTK] Refactor AddTorrent dialog update config scoping
- Fix a potential scoping issue with callback function by moving to
class method and adding requried parameters.
- Remove unneeded return statements and variable.
2018-09-26 14:18:52 +01:00
Calum Lind
57ad9a25da [GTK] Fix AddTorrent dialog title count
Use the treemodel signals to keep the torrent count in the dialog
title correct.
2018-09-26 14:18:52 +01:00
Calum Lind
5a2990ff90 [Core] Handle already prefetching magnet metadata 2018-09-26 14:18:52 +01:00
Calum Lind
759a618f74 [Core] Tweaks to prefetch metadata method
- Disable the magnet from being auto_managed so it starts immediately.
- Reduce the default timeout to 30secs.
- Use the generic tempfile dir.
- Move callback method to be class method
2018-09-26 14:18:52 +01:00
Calum Lind
23f1cfc926 [Core] Assign magnet arg if magnet URI in filename
A magnet that prefetched the metadata is added as a normal torrent but
with the filename set to the magnet URI. So we need to assign the URI to
magnet arg and set filename to None before creating Torrent object.

Updated the is_magnet function to prevent AttributeError with None type.
2018-09-26 14:18:52 +01:00
Calum Lind
57ea5ef5da [GTK] Set default file priority to 4 in add dialog 2018-09-26 14:18:52 +01:00
Calum Lind
944dc1659f [Core] Fix UI file priorities out-of-sync with libtorrent
The file priorities were not updating correctly in the UI and it was
found that in lt 1.1 file priorities are now updated asynchonously so we
cannot get the values immediately. So only update the options
file_priorities if they are empty.
2018-09-26 14:18:52 +01:00
Calum Lind
2dc157578e [GTK] Refactor torrent_liststore appending in AddTorrentDialog
The add_from_torrent and add_from_magnet shared common code so refactor
into methods for reuse.
2018-09-26 11:08:30 +01:00
Calum Lind
8a59216061 [#2398|GTKUI] Fetch magnet files details in Add Dialog 2018-09-26 11:08:30 +01:00
Calum Lind
cc1807cf97 Fix travis docs failing with Sphinx 1.8.0
Sphinx pinned to 1.7

See sphinx-doc/sphinx#5417
2018-09-14 16:09:20 +01:00
Calum Lind
63b7f6d382 [Tests] Add pytest-twisted to tox deps 2018-09-14 16:06:06 +01:00
Calum Lind
5c4cbf58c5 [Tests] Fix UnicodeWarning for gzipped file comparison 2018-09-14 16:06:06 +01:00
Calum Lind
5959a24d4c [Lint] Flake8 cleanup 2018-09-14 16:06:06 +01:00
Calum Lind
d4023e7dde [Py2to3] More fixes for web ui 2018-09-14 16:06:06 +01:00
Calum Lind
0fd3c25684 [Py2to3] Fixes to display Web UI 2018-09-14 16:06:06 +01:00
Calum Lind
4125e35ebd [Py2to3] Fix test_tranfer strings should be bytes 2018-09-14 16:06:06 +01:00
Calum Lind
18d448d4a5 [Py2to3] Ensure httpdownloader saves data as UTF-8
Python 3 raised a decoding error with the google page which appears to be
encoded with 'latin-1', so extract the content charset to decode and
re-encode in 'utf-8'.
2018-09-14 16:06:06 +01:00
Calum Lind
d5133f789a [Py2to3] Fix opening torrent files in byte mode 2018-09-14 16:06:06 +01:00
Calum Lind
1cce6a297c [Py2to3] Further maketorrent fixes 2018-09-14 16:06:06 +01:00
Calum Lind
ad20ec62f2 [Py2to3] Fix TorrentInfo metainfo dict key lookups 2018-09-14 16:06:06 +01:00
Calum Lind
af2bed8a0f [Py2to3] Fix tests for maketorrent and metafile 2018-09-14 16:06:06 +01:00
Calum Lind
b93e868048 [Py2to3] Fix ui_entry default indicator 2018-09-14 16:06:06 +01:00
Calum Lind
8d90ae5ffb [Console] Fix cmdline output and tests 2018-09-14 16:06:06 +01:00
Calum Lind
ae4449642c [Py2to3] Fix log.warn deprecation warning 2018-09-14 16:06:06 +01:00
Calum Lind
bc2f4a30eb [Py2to3] Fix putChild requires bytes 2018-09-14 16:06:06 +01:00
Calum Lind
dc8766874e [Tests] Fix testing core.add_torrent_url on Py3 2018-09-14 16:06:06 +01:00
Calum Lind
a33171732d Upgrade Pipfile lock for twisted
Due to a security warning for cryptography update twisted and deps.
2018-09-14 10:54:22 +01:00
DjLegolas
b9a9e06c1d [WebUI][Daemon] Enhance TLS Security
This applies the following for both WebUI and Daemon:
1. Raised minimal TLS version to TLSv1.2
2. Added specific cipher suite list
3. Added support for ECDSA auth keys
4. Added support for ECDHE key exchange algorithm

We disabled the ability to perform TLS/SSL renegotiation and therefore
will prevent the clients from renegotiating, which can be exploit for
DoS attacks.

New security tests now will be skipped when running `pydef` and `trial`
testenvs. To run the test, use the testenv `security` or add the environment
variable `SECURITY_TESTS` before running the tests. Also should only run when
adding to the commit message the string `SECURITY_TEST`.
2018-09-06 19:14:13 +01:00
Metaa
456e720b75 [WebUI] Constrain large icons to fit sidebar properly
Add CSS background position and size for sidebar icons.

Without this change tracker icons that are too big, render as too big.
This restrains them to fit into the sidebar list item.
2018-09-06 18:57:28 +01:00
Calum Lind
ae9bbdbae7 [Core] Add tests for pausing and resuming torrents 2018-08-10 17:48:53 +01:00
Deepak
585ea88f1f [Core] Fix torrent pause/resume logic
If the torrent_id argument received in the pause or resume methods is not a string, the methods execute with the un-parsed input and then with parsed input on a second call. A key error exception is thrown from the first call, and the second call succeeds.
2018-08-10 17:09:50 +01:00
Deepak
f94f58918e [Core] Remove libtorrent deprecated resolve_countries
Libtorrent 1.1 no longer supports this so remove it.
2018-08-10 09:56:38 +01:00
Calum Lind
3fc97672de Fix the docs run failing on Travis
Likely that the deprecation warning from cryptography is causing the
setup.py sphinx build command to return an error so the tox/travis job
is marked as failing. Changing to calling the sphinx-build command
directly solves this.

Also updated the sphinx config for built-in napoleon and faster builds
using jobs option.
2018-07-28 10:26:02 +01:00
Chase Sterling
e8e649a030 Prevent time formatting crash when seconds were floats
Update docstring and tests for ftime supporting floats

Truncate rather than round floats in ftime
2018-07-27 07:26:37 +01:00
Calum Lind
1e6c02ae83 [Core] Fix get_eta returning float instead of int
Floor division will return a float if a float is provided so ensure int
when dividing by the stop_ratio. All other status values from libtorrent
are ints.

Added tests.
2018-07-16 16:25:08 +01:00
DjLegolas
b2e1f850d8 [WebUI] Handle missing gettext.js file
Removed the creation code of `gettext.js` and now it will just mock
the `_` function by being the identity function.
2018-07-16 16:22:42 +01:00
Calum Lind
8bfa2cacbb Cleanup docstrings in httpdownloader
Use the new google docstring style.
Keep line length to 80 chars and new lines for mult-line func params.
2018-07-15 11:58:16 +01:00
DjLegolas
c7e61f8c34 [HTTPDownloader] Refactor HTTPDownloader to Agent
As of twisted 16.7.0, `twisted.web.client.HTTPDownloader` have been
marked as deprecated.
This caused the tests results to show many lines of warnings about it.
This refactor uses `twisted.web.client.Agent`, as suggested by Twisted.
2018-07-15 11:58:16 +01:00
DjLegolas
089c667d7f [AutoAdd] Add WebUI interface 2018-07-12 19:18:08 +01:00
Calum Lind
ebb955934d [Console] Fix unhandled error in preferences
If the value is None then the len cannot be calculated so set to blank
string.
2018-07-12 16:46:35 +01:00
Calum Lind
c7567ddee4 [Console] Fix unhandled error in torrentactions
Fix calls to core pause and resume using the singular methods with a
list of torrent ids.

Fix and simplify the handling of errors from core.
2018-07-10 16:46:49 +01:00
Calum Lind
c655da38c8 [Console] Ensure time string is unicode 2018-07-10 16:46:49 +01:00
Calum Lind
4c0be7ddd4 [Common] Fix missing return for de/encode methods 2018-07-10 16:46:49 +01:00
Calum Lind
38961d4253 [Console] Fix char encoding in preferences 2018-07-10 16:46:49 +01:00
Calum Lind
6e81a11d8d [Console] Fix and refactor window.add_str code
- Replace usage of single char variable.
- Use max function to prevent negate lengths when calculating trim.
- Remove usage of insstr as the addstr exception when writing offscreen is
excpected behaviour so needs caught and passed. This fixes a similar
error with insstr.
2018-07-10 16:46:49 +01:00
Calum Lind
be02be75be [GTKUI|Py3] Ensure backward compatible pickle dumps 2018-07-01 20:34:42 +01:00
Calum Lind
7b5ed9f1d6 [Py3] Fix dict iter item deletion 2018-07-01 10:08:16 +01:00
Calum Lind
e626f9fece [Win32] Fix missing certs for HTTPS requests
The following error occured on Windows when switching to using HTTPS
url with Twisted Agent:
```
<class 'twisted.web._newclient.ResponseNeverReceived'>: [<twisted.python.failure.Failure OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]>
```

The fix is to install certifi and provide the path to the trust store as
env var for OpenSSL to pick up.

Also includes a simplication of the core test_listen_port code.
2018-06-29 10:29:30 +01:00
Calum Lind
3fab799dbf Fix flake8 trailing comma 2018-06-27 16:41:21 +01:00
Calum Lind
24c100d9b7 [Py3] Decode new_release version to unicode string 2018-06-27 16:41:21 +01:00
Calum Lind
9bc2f62c80 [Py3] Fix tranfer header first byte check
The index of a byte in Python 3 will return an integer so use slice for
compatibility with 2/3
2018-06-27 16:41:21 +01:00
Calum Lind
1fa2de066f Fix mistakes in test code 2018-06-27 16:41:21 +01:00
Calum Lind
ae0b070c1b [Py3] Fix sort and sorted issues
In Python 3 there is only the key functions available and cmp functions
should be removed, especially for speed.
2018-06-27 16:41:21 +01:00
Calum Lind
c3a2c67b98 [Py3] A large set of fixes for tests to pass under Python 3
The usual minor fixes for unicode/bytes for library calls.

The minimum Twisted version is now 16 for Python 3 support so remove old
code and start replacing deprecated methods.

Raised the minimum TLS version to 1.2 for the web server.
2018-06-27 16:41:21 +01:00
Calum Lind
200e8f552b Add dependecy on six 2018-06-27 16:41:21 +01:00
Calum Lind
4247013446 [bencode] Fix errors with unicode dict keys or values 2018-06-26 12:42:26 +01:00
Calum Lind
6ec32a85e4 [GTKUI] Fix AttributeError with clipboard strip 2018-06-26 12:42:26 +01:00
Calum Lind
633c56f54e [Core] Add prefetch metadata methods for magnets 2018-06-26 12:42:26 +01:00
Calum Lind
23171ad205 Fix missing _metainfo attribute in TorrentInfo 2018-06-26 12:42:26 +01:00
Calum Lind
277576268c [Py2to3] Replace deprecated base64.(de|en)codestring
* In Py3 base64.encodestring is deprecated so rather than use the
   Py3 only encodebytes instead use b64encode. The other advantage is
   that with issue a consistent TypeError is raised that we can catch.
2018-06-26 12:42:26 +01:00
Calum Lind
74aa0db956 Manually compress png with zopflipng 2018-06-21 14:50:19 +01:00
Calum Lind
fe42fb2c31 Recreate and compress icons
Will the change to deluge.svg all the icons need recreated. I also
updated the script to losslessly compress the png files with zopflipng.

hicolor theme changes:
 - Added a 512px icon.
 - Added a deluge-panel.png for systray theming.

Added extra webui icons and updated index.html to use them
correctly.
2018-06-21 14:50:19 +01:00
Calum Lind
4973538d6c Tweak the deluge icon size
At smaller sizes the padding makes the icon too small so only keep the
padding at the bottom and let the point of the droplet extend to top of
canvas.
2018-06-21 14:40:52 +01:00
DjLegolas
de1e7c27df [#2867][WebUI] Fix Daemon connection problem
Trying to connect to daemon B while still connected to A will cause
the torrents from A to be shown after connecting to B.
Therefor, checking if connected to any daemon before connecting to B.
2018-06-19 09:39:15 +01:00
Calum Lind
587b9afefe [GTKUI] Fix count of torrents in dialog title 2018-06-19 09:34:46 +01:00
Calum Lind
63b25311f5 [UI] Refactor TorrentInfo and add functionality
Make a clearer distinction about torrent metainfo and metadata and allow
passing these to TorrentInfo.
2018-06-19 09:12:57 +01:00
Calum Lind
d45dbfe064 [Core] Add is_session_paused method 2018-06-18 20:06:35 +01:00
Calum Lind
3176b877a4 [Core] Add methods pause_torrents & resume_torrents 2018-06-18 20:06:35 +01:00
sam-xif
18541bce86 Fixed a minor grammatical error in the deluge console command documentation 2018-06-18 20:04:22 +01:00
Kirill Romanov
bebe08d92b Add X-GNOME-UsesNotifications
Due to Gnome Guidelines https://wiki.gnome.org/Initiatives/GnomeGoals/NotificationSource
2018-06-18 20:00:49 +01:00
DjLegolas
0dbbb51cff [Core] Fix strip None in set_listen_on
Trying to strip None will not work so combined the check as a condition
for the strip.
2018-06-18 19:56:16 +01:00
Calum Lind
bd78bd2643 [#3001|GTK] Fix sorting to default to Added column
The sort function used when no column is being sorted was a lambda
function that had no effect and had a bug. Instead default to sort by
the added date.

Also fixed the name column sort to lowercase and uppercase properly.
2018-06-17 08:34:17 +01:00
DjLegolas
7a3b164060 [WebUi] Fixed Install Plugin window creation 2018-06-12 03:50:30 +03:00
Calum Lind
e7eb26416e [Core] Fix strip None in outgoing_interface
Trying to strip None will not work so do this after checks for
falseness.
2018-06-09 22:18:38 +01:00
Calum Lind
b2b7703081 [AutoAdd] Fix the logging for a failed added torrent
Add a missing arg to the failed added torrent callback and update the
logging text based on magnet or not.
2018-06-09 22:18:38 +01:00
Calum Lind
cbdde7bba5 [Appveyor] Make win32 build files available as zip
It can be useful to have the build folder available so zip it up for
download as an artifact.

Although the 7z has an exclude for the installer exe it doesn't seem to
be working.
2018-06-05 23:21:12 +01:00
Unknown
4fd51a4ef9 Skip Failing Tests On Windows 2018-06-02 22:09:50 +01:00
Unknown
333c81c1d7 Add Appveyor support for Windows builds 2018-06-02 22:09:50 +01:00
Calum Lind
21b5a15e5d Fix and cleanup outgoing interface code
There was a misunderstand about outgoing interface setting in libtorrent
and instead of being able to take both IP and adapater names, it only
accepts adapter names and errors with an IP address, which was the
default of '0.0.0.0' in code.

This fixes the code to not accept IP address and use empty string if it
is given one.

Also includes a bit of code cleanup.
2018-06-02 21:32:56 +01:00
Unknown
edd431a304 Make Tox Multi-OS Friendly 2018-06-02 11:29:51 +01:00
Calum Lind
d642fa3989 Fix files to pass new Flake8 checkers
Some new flake8 checkers were added so fix these new warnings and
any issues uncovered.

Use add-trailing-comma to fix missing trailing commas. It does not
format it as well as I would like however it was fast to change and
helps with git changes in future.

Removed pylint from tox due to large number of warnings.
2018-06-01 23:41:17 +01:00
Calum Lind
bae1647e99 Add Pipenv Pipfile for development
Switching to Pipenv will speed up developement. See the docs for
details on using Pipenv.

 - Added more flake8 checks.
 - Added `detox` for running tests in parallel locally.
2018-06-01 23:41:17 +01:00
Calum Lind
decd7aca71 Update gettext for outgoing interface 2018-06-01 12:57:18 +01:00
Calum Lind
7cc9aaca49 Fix VersionSplit comparison
The tests on Python revealed a bug with comparing dev versions.

Switch to comparing by integers and setting non-dev version to infinity.

There is still an issue with suffix release comparisons beyond single
digits but will leave that for now.
2018-06-01 08:59:24 +01:00
Doadin
196aa48727 [#3171] Add Option To Specify Outgoing Connection Interface 2018-05-21 13:14:15 -04:00
Calum Lind
af2972f697 [GTKUI] Update prefs dialog ui file with Glade
Let Glade UI designer remove and make changes to UI file.
2018-05-21 08:30:25 +01:00
Calum Lind
d4addeedd6 [UI] Fix non-unique hostlist host_id
Use a sha1 of time.time() can result in identical host_id. This was
evident with Travis tests randomly failing due to host_id collision
returning the wrong host details.

Using uuid4 to generate a random UUID in hex form should fix this issue.
2018-05-20 22:49:08 +01:00
Calum Lind
8439698336 [Tox] Use platform independant toxworkdir instead of PWD
The use of `{env:PWD}` is not available on Windows so switch to
`{toxworkdir}` which is the directory where virtual environments
are created and sub directories for packaging reside.
2018-05-20 08:46:40 +01:00
Calum Lind
7d120690ab [Docs] Fix Sphinx AutoDoc failing with gdk mask operation
The creation of a new mask from two gdk mask is causing a TypeError,
likely related to the mocking of gdk in Sphinx conf.
2018-05-20 08:46:25 +01:00
Calum Lind
ee196f5035 [Flake8] Fix import and docstrings issues 2018-05-20 08:45:59 +01:00
Calum Lind
ff85c334c7 [Tests] Fix 'Too many open files' by disabling LSD
Instances of libtorrent with Local Service Discovery enabled are leaving
many sockets fd open with every test run and will fail with 'Too many files
open' if ulimit is >=1024.
2018-05-19 21:50:23 +01:00
Calum Lind
0c574f33e1 [Tests] Ensure tear_down deletes rpcserver and core 2018-05-19 21:23:46 +01:00
Calum Lind
a7c7309027 Use context manager for open in metafile 2018-05-19 21:03:36 +01:00
Calum Lind
de2f998218 [WebUI] Encode HTML entitiies
Ensure that torrent keys that could contain HTML entities are encoded
when displayed in webui.
2018-02-04 22:02:18 +00:00
Calum Lind
4982ba0b98 Cleanup pytest config in tox
Config values are either wrong, unused or default is better.

Disable logging of deluge log output in testing.

Newer versions of pytest now use `pytest` cmd so rename.
2017-12-17 11:55:09 +00:00
Calum Lind
f57286fd51 [#3121] Fix the peer-id to be unique
The associated ticket made us aware that the id '-DE2000-' might be
blocked.
2017-12-17 11:55:09 +00:00
Calum Lind
12f7345d0c Use a constant for versions 2017-12-17 11:47:08 +00:00
Calum Lind
4e79ed8124 Fix tox config to install latest pip packages
Trial changed command in latest versions of twisted
2017-12-16 17:39:48 +00:00
Calum Lind
7787aa975f Delay assert in test_torrent for Travis 2017-12-16 17:29:30 +00:00
Calum Lind
c13622a1e6 Fix state not loading after async API change 2017-12-16 13:21:47 +00:00
Calum Lind
07a87fa15a [#3129|Console] Fix unable to use connect command from terminal
The parsed_cmd passed to do_command was an argparse Namespace object
which needed no further parsing so use exec_command instead.
2017-11-18 23:46:35 +00:00
Calum Lind
2644169376 [#3126|Core] Restore synchonous add torrent methods for backward compatibilty
The synchonous add torrent method was replaced with async but this
break backward compatibility with 3rd party plugins and clients.

Added a new add_torrent_file_async method for adding single torrent.

Torrent manager has a new add_async method and split up code to prevent
duplication.

Update any use of add_torrent_file to add_torrent_file_async. Future
refactoring could use add_torrent_files instead.
2017-11-18 23:04:38 +00:00
Calum Lind
5988f5f04f Fix flake8 error 2017-11-18 22:14:04 +00:00
Calum Lind
95d826b77c [#3127|Blocklist] Fix importing blocklist with encoded lines
There are some blocklists with encoded names that break upon importing
so decode lines to unicode.

Need to use decode_bytes as not all encoded lines are utf8!
2017-11-05 20:52:51 +00:00
Calum Lind
9bcda41700 [#3075|Console] Fix config handling windows paths
The console config token parser was unable to handle windows paths
starting with 'C:\'.

Remove unneeded windows_checks.
2017-11-05 17:30:47 +00:00
Calum Lind
507c5df984 [#3112|Console] Fix handling hex for setting peer_tos in config
The token parser was converting hex value to int which is not what
should be passed onto libtorrent peer_tos setting.
2017-10-29 22:16:13 +00:00
Calum Lind
0ba87b424c Revert "[#2848|Core] Fix incorrect share ratio in torrent status"
This reverts commit 7b87a93862.

After further discussion in the ticket this change is undesired.

> I wrongly assumed that private trackers will count how many bytes you
> download but they don't, they track how many parts(or chunks?) you
> have, when you announce it. So using total_done is fine, no change
> needed.

> Checking for private flag and using total_wanted_done for public
> enables users to be a bit more selfish. They can start a torrent,
> let it run for a bit then deselect the files they don't want and
> only upload enough to make up for what they wanted to download. This
> means they may upload less than they downloaded, hurting the swarm.
> So I personally don't think this would be a good change.
> Overall my suggestion is to close this as not a bug.
2017-10-29 12:36:15 +00:00
Calum Lind
53f818e176 [#3070] Fix httpdownloader error with missing content-disposition filename
The parsing of the content-disposition in httpdownloader was not able to
handle missing parameters e.g. "Content-Disposition: attachment" and would
result in an IndexError. Added a test for this use-case.

Fixed the issue using the cgi.parse_header to extract the parameters.
2017-10-29 12:32:32 +00:00
Calum Lind
00dcd60d56 [Lint] Fix flake8 issues with l as var 2017-10-29 11:39:52 +00:00
Calum Lind
1730230244 [#3124|GTKUI] Fix comparing Name str if value is None
The original fix was not correct as the strcoll function cannot
accept None only strings. This fix ensures that the value is an
empty string if None for comparison.
2017-10-29 11:16:13 +00:00
Calum Lind
0728c03c1c [#3066|Core] Add rather than replace dht bootstrap nodes in lt 2017-10-29 10:36:45 +00:00
Calum Lind
354372b2ea [Notifications] Remove duplicate heading on prefs page
The heading is provided by prefs add_page so remove from glade file.
2017-10-16 21:57:08 +01:00
DjLegolas
d169aca8bd [Notifications]Fix no text in tab list
The tab in the Preferences window is created and clickable - only the
text is missing.
2017-10-16 14:15:17 +03:00
Calum Lind
26720ca4c2 [AutoAdd] Fix handling deferred torrents
* The changes to core api now return a deferred instead of torrent_id
   so need to update autoadd to use callbacks.
 * Minor refactor to save one indentation level and reuse fail callback.
 * Add exception handler for any errors from label plugin.
2017-10-14 21:39:30 +01:00
DjLegolas
510a8b50b2 [AutoAdd] Update gtkui from libglade to gtkbuilder 2017-10-14 21:30:45 +01:00
DjLegolas
d190f149d1 [WebUi] Update gtkui from libglade to gtkbuilder 2017-10-14 20:13:53 +01:00
DjLegolas
24a31b1194 [Notifications] Update gtkui from libglade to gtkbuilder 2017-10-14 20:13:53 +01:00
DjLegolas
470490769f [Extractor] Update gtkui from libglade to gtkbuilder 2017-10-14 20:13:53 +01:00
DjLegolas
1259eca8ad [Execute] Update gtkui from libglade to gtkbuilder 2017-10-14 20:13:53 +01:00
DjLegolas
f0316d3e31 [Blocklist] Update gtkui from libglade to gtkbuilder 2017-10-14 20:13:53 +01:00
bendikro
9b580a87fa [GKTUI] Fix high priority files tab trigger 2017-10-14 20:06:14 +01:00
Calum Lind
4bee1ce811 [Travis] Use current Trusty image
The issue with virtualenv site-packages was fixed: travis-ci/travis-cookbooks#878
2017-10-14 20:00:14 +01:00
Calum Lind
e3f537770f [UI] Fix setting gettext to unicode for Py2 compat 2017-07-05 12:18:35 +01:00
Calum Lind
9b92bc2baf [#3076|UI] Add ngettext and alias _n for plural translations
* Fixes use of _n() in console rm command
2017-07-05 09:46:12 +01:00
Calum Lind
51b99caf24 [Common] Add decode_string (deprecated) for compatibility 2017-06-29 15:28:52 +01:00
Calum Lind
850fd34522 [#3084] Fix error changing ownership on torrents 2017-06-29 15:07:11 +01:00
Calum Lind
9164dafe69 [#3083] Fix missing common.utf8_encoded for backward compatibility 2017-06-29 14:42:16 +01:00
Calum Lind
33e9545cd4 [#3079] Fix config parsing for json objects
* If a curly brace was used in a string then find_json_ojects would
   fail to find objects correctly. To fix this ignore double-quoted entries.
2017-06-28 10:32:45 +01:00
Calum Lind
7b87a93862 [#2848|Core] Fix incorrect share ratio in torrent status
* Use total_wanted_done to increase the accuracy of the calculated ratio.
2017-06-27 19:02:15 +01:00
Calum Lind
51bde704b5 [Packaging] Simplify release script using sdist
* setup.py sdist now creates a pristine tar which can be used for release.
 * Uses the version currently checked-out in git.
 * Removed unneeded lines in manifest.
2017-06-27 18:12:51 +01:00
Calum Lind
3f13c24362 Update MANIFEST for wanted/unwanted files 2017-06-27 15:12:13 +01:00
Calum Lind
6837d83f5b Fix setup.py requiring gen_web_gettext 2017-06-27 15:11:19 +01:00
Calum Lind
3c1d7da698 [Packaging] Apply fixes to OSX app scripts
* Fix path to dist dir
 * Rename dylib with new soversion for lt 1.1.x
 * Create Info.plist with version and year automatically
2017-06-27 14:00:06 +01:00
Calum Lind
d6731b8cee [#3078|GTKUI] Apply workaround for showing the OSX menu
Commenting out the remove_accelerator fixes showing the menubar correctly.
2017-06-27 09:16:51 +01:00
Calum Lind
fe80703f95 [Packaging] Fix py2app build 2017-06-27 09:16:51 +01:00
Calum Lind
1808ac506a [GTKUI] Restore removed os_check for systray icon
status_icon_new_from_icon_name does not work on OSX
2017-06-27 09:16:51 +01:00
Calum Lind
3174c7534d [Core] Ensure tracker error message is decoded 2017-06-27 09:16:51 +01:00
Calum Lind
065729a389 [OSX] Fix converting mac_ver to string 2017-06-27 09:16:51 +01:00
1086 changed files with 474484 additions and 279431 deletions

2
.gitattributes vendored
View file

@ -2,3 +2,5 @@
.gitmodules export-ignore
.gitignore export-ignore
*.py diff=python
ext-all.js diff=minjs
*.state -merge -text

104
.github/workflows/cd.yml vendored Normal file
View file

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

101
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,101 @@
name: CI
on:
push:
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
inputs:
core-dump:
description: "Set to 1 to enable retrieving core dump from crashes"
default: "0"
jobs:
test-linux:
runs-on: ubuntu-22.04
strategy:
matrix:
python-version: ["3.7", "3.10"]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "requirements*.txt"
- name: Sets env var for security
if: (github.event_name == 'pull_request' && contains(github.event.pull_request.body, 'security_test')) || (github.event_name == 'push' && contains(github.event.head_commit.message, 'security_test'))
run: echo "SECURITY_TESTS=True" >> $GITHUB_ENV
- name: Install dependencies
run: |
pip install --upgrade pip wheel setuptools
pip install -r requirements-ci.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 catch and store
if: github.event.inputs.core-dump == '1'
run: |
sudo mkdir /cores/ && sudo chmod 777 /cores/
echo "/cores/%E.%p" | sudo tee /proc/sys/kernel/core_pattern
ulimit -c unlimited
sudo apt install glibc-tools
echo "DEBUG_PREFIX=catchsegv python -X dev -m" >> $GITHUB_ENV
- name: Test with pytest
run: |
python -c 'from deluge._libtorrent import lt; print(lt.__version__)';
$DEBUG_PREFIX pytest -v -m "not (todo or gtkui)" deluge
- uses: actions/upload-artifact@v4
# capture all crashes as build artifacts
if: failure()
with:
name: crashes
path: /cores
test-windows:
runs-on: windows-2022
strategy:
matrix:
python-version: ["3.7", "3.10"]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "requirements*.txt"
- name: Install dependencies
run: |
pip install --upgrade pip wheel setuptools
pip install -r requirements-ci.txt
pip install -e .
- name: Test with pytest
run: |
python -c 'import libtorrent as lt; print(lt.__version__)';
pytest -v -m "not (todo or gtkui or security)" deluge

38
.github/workflows/docs.yml vendored Normal file
View file

@ -0,0 +1,38 @@
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@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: "pip"
cache-dependency-path: "requirements*.txt"
- name: Install dependencies
run: |
pip install --upgrade pip wheel
pip install tox
sudo apt-get install enchant-2
- name: Build docs with tox
env:
TOX_ENV: docs
run: |
tox -e $TOX_ENV

17
.github/workflows/lint.yml vendored Normal file
View 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@v4
- uses: actions/setup-python@v5
- name: Run pre-commit linting
uses: pre-commit/action@v3.0.1

18
.gitignore vendored
View file

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

41
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,41 @@
default_language_version:
python: python3
exclude: >
(?x)^(
deluge/ui/web/docs/template/.*|
deluge/tests/data/.*svg|
)$
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.6.4
hooks:
- id: ruff
name: Chk Ruff
args: [--fix]
- id: ruff-format
name: Fmt Ruff
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
- id: prettier
name: Fmt Prettier
# Workaround to list modified files only.
args: [--list-different]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: end-of-file-fixer
name: Fix End-of-files
exclude_types: [javascript, css]
- id: mixed-line-ending
name: Fix Line endings
args: [--fix=auto]
- id: trailing-whitespace
name: Fix Trailing whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py37-plus]
stages: [manual]

6
.prettierignore Normal file
View file

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

13
.prettierrc.yaml Normal file
View file

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

View file

@ -69,7 +69,7 @@ confidence=
# Arranged by category and use symbolic names instead of ids.
disable=
# Convention
missing-docstring, invalid-name,
missing-docstring, invalid-name, bad-continuation,
# Error
no-member, no-name-in-module,
# Information
@ -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]

29
.readthedocs.yml Normal file
View file

@ -0,0 +1,29 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.10"
jobs:
post_checkout:
- git fetch --unshallow || true
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Optionally build your docs in additional formats such as PDF and ePub
formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
install:
- requirements: requirements.txt
- requirements: docs/requirements.txt
- method: pip
path: .

View file

@ -1,51 +0,0 @@
dist: trusty
sudo: required
group: deprecated-2017Q2
language: python
python:
- "2.7"
cache: pip
before_install:
- lsb_release -a
- sudo add-apt-repository ppa:deluge-team/develop -y
- sudo apt-get update
# command to install dependencies
install:
- bash -c "echo $APTPACKAGES"
- sudo apt-get install $APTPACKAGES
- pip install "tox==2.1.1"
env:
global:
- APTPACKAGES="python-libtorrent"
- APTPACKAGES_GTKUI="python-gobject python-glade2"
- DISPLAY=:99.0
matrix:
- TOX_ENV=pydef
- TOX_ENV=flake8
# - TOX_ENV=flake8-complexity
- TOX_ENV=docs
# - TOX_ENV=todo
- TOX_ENV=trial APTPACKAGES="$APTPACKAGES $APTPACKAGES_GTKUI"
- TOX_ENV=pygtkui APTPACKAGES="$APTPACKAGES $APTPACKAGES_GTKUI"
# - TOX_ENV=testcoverage APTPACKAGES="$APTPACKAGES $APTPACKAGES_GTKUI"
- TOX_ENV=plugins
virtualenv:
system_site_packages: true
# We use xvfb for the GTKUI tests
before_script:
- export PYTHONPATH=$PYTHONPATH:$PWD
- python -c "import libtorrent as lt; print lt.__version__"
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
- echo '2.0.0.dev0' > RELEASE-VERSION
script:
- bash -c "echo $DISPLAY"
- tox -e $TOX_ENV

17
AUTHORS
View file

@ -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>

269
CHANGELOG.md Normal file
View file

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

View file

@ -1,50 +0,0 @@
=== Deluge 2.0 (In Development) ===
* Improved Logging
* Removed the AutoAdd feature on the core. It's now handled with the AutoAdd
plugin, which is also shipped with Deluge, and it does a better job and
now, it even supports multiple users perfectly.
* Authentication/Permission exceptions are now sent to clients and recreated
there to allow acting upon them.
* Enforced the use of the "deluge.plugins" namespace to reduce package
names clashing beetween regular packages and deluge plugins.
==== Core ====
* Make the distinction between adding to the session new unmanaged torrents
and torrents loaded from state. This will break backwards compatability.
* Pass a copy of an event instead of passing the event arguments to the
event handlers. This will break backwards compatability.
* 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.
* Implemented sequential downloads.
* Provide information about a torrent's pieces states
==== GtkUI ====
* Allow changing ownership of torrents.
* Host entries in the Connection Manager UI are now editable.
* Implemented sequential downloads UI handling.
* Add optional pieces bar instead of a regular progress bar in torrent status tab.
* Make torrent opening compatible with all unicode paths.
* Fix magnet association button on Windows.
* Add keyboard shortcuts for changing queue position:
- Up: Ctrl+Alt+Up
- Down: Ctrl+Alt+Down
- Top: Ctrl+Alt+Shift+Up
- Bottom: Ctrl+Alt+Shift+Down
==== WebUI ====
* Server (deluge-web) now daemonizes by default, use '-d' or '--do-not-daemonize' to disable.
* Fixed the '--base' option to work for regular use, not just with reverse proxies.
==== Blocklist Plugin ====
* Implemented whitelist support to both core and GTK UI.
* Implemented ip filter cleaning before each update. Restarting the deluge
daemon is no longer needed.
* If "check_after_days" is 0(zero), the timer is not started anymore. It
would keep updating one call after the other. If the value changed, the
timer is now stopped and restarted using the new value.

29
DEPENDS
View file

@ -1,29 +0,0 @@
=== Core ===
* libtorrent (rasterbar) >= 1.1.1
* python >= 2.7.7
* setuptools
* twisted >= 11.1
* pyopenssl
* pyxdg
* chardet
* gettext
* python-geoip (optional)
* geoip-database (optional)
* setproctitle (optional)
* pillow (optional)
* py2-ipaddress (optional, required for Windows IPv6)
* rencode >= 1.0.2 (optional), python port bundled.
=== Gtk UI ===
* pygtk >= 2.16
* librsvg
* xdg-utils
* intltool
* python-notify (optional)
* pygame (optional)
* python-appindicator (optional)
=== Web UI ===
* mako
* slimit (optional), minifies JS files.

100
DEPENDS.md Normal file
View file

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

View file

@ -1,23 +1,29 @@
include AUTHORS ChangeLog DEPENDS LICENSE RELEASE-VERSION README.rst
include msgfmt.py minify_web_js.py version.py
exclude setup.cfg
include *.md
include AUTHORS
include LICENSE
include RELEASE-VERSION
include msgfmt.py
include minify_web_js.py
include version.py
include gen_web_gettext.py
graft docs/man
graft packaging/systemd
include deluge/i18n/*.po
recursive-exclude deluge/i18n LC_MESSAGES *.mo
recursive-exclude deluge/i18n *.mo
graft deluge/plugins
recursive-exclude deluge/plugins create_dev_link.sh *.pyc *.egg
prune deluge/plugins/*/build
prune deluge/plugins/*/*.egg-info
graft deluge/tests/data
graft deluge/tests/twisted
graft deluge/tests/
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

71
README.md Normal file
View file

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

View file

@ -1,68 +0,0 @@
=========================
Deluge BitTorrent Client
=========================
|build-status| |docs|
Homepage: http://deluge-torrent.org
Authors:
Andrew Resch
Damien Churchill
For contributors and past developers see:
AUTHORS
==========================
Installation Instructions:
==========================
For detailed instructions see: http://dev.deluge-torrent.org/wiki/Installing/Source
Ensure build dependencies are installed, see DEPENDS for a full listing.
Build and install by running::
$ python setup.py build
$ sudo python setup.py install
================
Contact/Support:
================
:Forum: http://forum.deluge-torrent.org
:IRC Channel: #deluge on irc.freenode.net
===
FAQ
===
For the full FAQ see: http://dev.deluge-torrent.org/wiki/Faq
How to start the various user-interfaces:
Gtk::
deluge or deluge-gtk
Console::
deluge-console
Web::
deluge-web
Go to http://localhost:8112/ default-password = "deluge"
How do I start the daemon?:
deluged
I can't connect to the daemon from another machine:
See: http://dev.deluge-torrent.org/wiki/UserGuide/ThinClient
.. |build-status| image:: https://travis-ci.org/deluge-torrent/deluge.svg
:target: https://travis-ci.org/deluge-torrent/deluge
.. |docs| image:: https://readthedocs.org/projects/deluge/badge/?version=develop
:target: https://readthedocs.org/projects/deluge/?badge=develop
:alt: Documentation Status

6
__builtins__.pyi Normal file
View file

@ -0,0 +1,6 @@
from twisted.web.http import Request
__request__: Request
def _(string: str) -> str: ...
def _n(string: str) -> str: ...

View file

@ -1,7 +1 @@
"""Deluge"""
from __future__ import unicode_literals
# this is a namespace package
import pkg_resources
pkg_resources.declare_namespace(__name__)

View file

@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
from __future__ import unicode_literals
from deluge.core.core import Core
from deluge.core.daemon import Daemon
class RpcApi(object):
pass
def scan_for_methods(obj):
methods = {
'__doc__': 'Methods available in %s' % obj.__name__.lower()
}
for d in dir(obj):
if not hasattr(getattr(obj, d), '_rpcserver_export'):
continue
methods[d] = getattr(obj, d)
cobj = type(obj.__name__.lower(), (object,), methods)
setattr(RpcApi, obj.__name__.lower(), cobj)
scan_for_methods(Core)
scan_for_methods(Daemon)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@ -15,16 +14,23 @@ 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'
if VersionSplit(lt.__version__) < VersionSplit(REQUIRED_VERSION):
raise ImportError('Deluge %s requires libtorrent >= %s' % (get_version(), REQUIRED_VERSION))
REQUIRED_VERSION = '1.2.0.0'
LT_VERSION = lt.__version__
if VersionSplit(LT_VERSION) < VersionSplit(REQUIRED_VERSION):
raise LibtorrentImportError(
f'Deluge {get_version()} requires libtorrent >= {REQUIRED_VERSION}'
)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@ -7,8 +6,6 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import argparse
import logging
import os
@ -86,15 +83,16 @@ argparse.ArgumentParser.find_subcommand = find_subcommand
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def get_version():
def _get_version_detail():
version_str = '%s\n' % (common.get_version())
try:
from deluge._libtorrent import lt
version_str += 'libtorrent: %s\n' % lt.__version__
from deluge._libtorrent import LT_VERSION
version_str += 'libtorrent: %s\n' % LT_VERSION
except ImportError:
pass
version_str += 'Python: %s\n' % platform.python_version()
version_str += 'OS: %s %s\n' % (platform.system(), ' '.join(common.get_os_version()))
version_str += f'OS: {platform.system()} {common.get_os_version()}\n'
return version_str
@ -108,8 +106,8 @@ class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
line instead. This way list formatting is not mangled by textwrap.wrap.
"""
wrapped_lines = []
for l in text.splitlines():
wrapped_lines.extend(textwrap.wrap(l, width, subsequent_indent=' '))
for line in text.splitlines():
wrapped_lines.extend(textwrap.wrap(line, width, subsequent_indent=' '))
return wrapped_lines
def _format_action_invocation(self, action):
@ -121,7 +119,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 = []
@ -136,12 +134,11 @@ class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
default = action.dest.upper()
args_string = self._format_args(action, default)
opt = ', '.join(action.option_strings)
parts.append('%s %s' % (opt, args_string))
parts.append(f'{opt} {args_string}')
return ', '.join(parts)
class HelpAction(argparse._HelpAction):
def __call__(self, parser, namespace, values, option_string=None):
if hasattr(parser, 'subparser'):
subparser = getattr(parser, 'subparser')
@ -151,11 +148,12 @@ class HelpAction(argparse._HelpAction):
parser.exit()
class BaseArgParser(argparse.ArgumentParser):
class ArgParserBase(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
if 'formatter_class' not in kwargs:
kwargs['formatter_class'] = lambda prog: DelugeTextHelpFormatter(prog, max_help_position=33, width=90)
kwargs['formatter_class'] = lambda prog: DelugeTextHelpFormatter(
prog, max_help_position=33, width=90
)
kwargs['add_help'] = kwargs.get('add_help', False)
common_help = kwargs.pop('common_help', True)
@ -164,32 +162,73 @@ class BaseArgParser(argparse.ArgumentParser):
self.log_stream = kwargs['log_stream']
del kwargs['log_stream']
super(BaseArgParser, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.common_setup = False
self.process_arg_group = False
self.group = self.add_argument_group(_('Common Options'))
if common_help:
self.group.add_argument('-h', '--help', action=HelpAction,
help=_('Print this help message'))
self.group.add_argument('-V', '--version', action='version', version='%(prog)s ' + get_version(),
help=_('Print version information'))
self.group.add_argument('-v', action='version', version='%(prog)s ' + get_version(),
help=argparse.SUPPRESS) # Deprecated arg
self.group.add_argument('-c', '--config', metavar='<config>',
help=_('Set the config directory path'))
self.group.add_argument('-l', '--logfile', metavar='<logfile>',
help=_('Output to specified logfile instead of stdout'))
self.group.add_argument('-L', '--loglevel', choices=[l for k in deluge.log.levels for l in (k, k.upper())],
help=_('Set the log level (none, error, warning, info, debug)'), metavar='<level>')
self.group.add_argument('--logrotate', nargs='?', const='2M', metavar='<max-size>',
help=_('Enable logfile rotation, with optional maximum logfile size, '
'default: %(const)s (Logfile rotation count is 5)'))
self.group.add_argument('-q', '--quiet', action='store_true',
help=_('Quieten logging output (Same as `--loglevel none`)'))
self.group.add_argument('--profile', metavar='<profile-file>', nargs='?', default=False,
help=_('Profile %(prog)s with cProfile. Outputs to stdout '
'unless a filename is specified'))
self.group.add_argument(
'-h', '--help', action=HelpAction, help=_('Print this help message')
)
self.group.add_argument(
'-V',
'--version',
action='version',
version='%(prog)s ' + _get_version_detail(),
help=_('Print version information'),
)
self.group.add_argument(
'-v',
action='version',
version='%(prog)s ' + _get_version_detail(),
help=argparse.SUPPRESS,
) # Deprecated arg
self.group.add_argument(
'-c',
'--config',
metavar='<config>',
help=_('Set the config directory path'),
)
self.group.add_argument(
'-l',
'--logfile',
metavar='<logfile>',
help=_('Output to specified logfile instead of stdout'),
)
self.group.add_argument(
'-L',
'--loglevel',
choices=[level for k in deluge.log.levels for level in (k, k.upper())],
help=_('Set the log level (none, error, warning, info, debug)'),
metavar='<level>',
)
self.group.add_argument(
'--logrotate',
nargs='?',
const='2M',
metavar='<max-size>',
help=_(
'Enable logfile rotation, with optional maximum logfile size, '
'default: %(const)s (Logfile rotation count is 5)'
),
)
self.group.add_argument(
'-q',
'--quiet',
action='store_true',
help=_('Quieten logging output (Same as `--loglevel none`)'),
)
self.group.add_argument(
'--profile',
metavar='<profile-file>',
nargs='?',
default=False,
help=_(
'Profile %(prog)s with cProfile. Outputs to stdout '
'unless a filename is specified'
),
)
def parse_args(self, args=None):
"""Parse UI arguments and handle common and process group options.
@ -204,7 +243,7 @@ class BaseArgParser(argparse.ArgumentParser):
argparse.Namespace: The parsed arguments.
"""
options = super(BaseArgParser, self).parse_args(args=args)
options = super().parse_args(args=args)
return self._handle_ui_options(options)
def parse_known_ui_args(self, args, withhold=None):
@ -220,9 +259,9 @@ class BaseArgParser(argparse.ArgumentParser):
"""
if withhold:
args = [a for a in args if a not in withhold]
options, remaining = super(BaseArgParser, self).parse_known_args(args=args)
options, remaining = super().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):
@ -251,8 +290,13 @@ class BaseArgParser(argparse.ArgumentParser):
logrotate = common.parse_human_size(options.logrotate)
# Setup the logger
deluge.log.setup_logger(level=options.loglevel, filename=options.logfile, filemode=logfile_mode,
logrotate=logrotate, output_stream=self.log_stream)
deluge.log.setup_logger(
level=options.loglevel,
filename=options.logfile,
filemode=logfile_mode,
logrotate=logrotate,
output_stream=self.log_stream,
)
if options.config:
if not set_config_dir(options.config):
@ -278,20 +322,22 @@ class BaseArgParser(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.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)
os.setgid(options.group)
if options.user:
if not options.user.isdigit():
import pwd
options.user = pwd.getpwnam(options.user)[2]
os.setuid(options.user)
return options
@ -300,14 +346,39 @@ class BaseArgParser(argparse.ArgumentParser):
self.process_arg_group = True
self.group = self.add_argument_group(_('Process Control Options'))
self.group.add_argument('-P', '--pidfile', metavar='<pidfile>', action='store',
help=_('Pidfile to store the process id'))
self.group.add_argument(
'-P',
'--pidfile',
metavar='<pidfile>',
action='store',
help=_('Pidfile to store the process id'),
)
if not common.windows_check():
self.group.add_argument('-d', '--do-not-daemonize', dest='donotdaemonize', action='store_true',
help=_('Do not daemonize (fork) this process'))
self.group.add_argument('-f', '--fork', dest='donotdaemonize', action='store_false',
help=argparse.SUPPRESS) # Deprecated arg
self.group.add_argument('-U', '--user', metavar='<user>', action='store',
help=_('Change to this user on startup (Requires root)'))
self.group.add_argument('-g', '--group', metavar='<group>', action='store',
help=_('Change to this group on startup (Requires root)'))
self.group.add_argument(
'-d',
'--do-not-daemonize',
dest='donotdaemonize',
action='store_true',
help=_('Do not daemonize (fork) this process'),
)
self.group.add_argument(
'-f',
'--fork',
dest='donotdaemonize',
action='store_false',
help=argparse.SUPPRESS,
) # Deprecated arg
self.group.add_argument(
'-U',
'--user',
metavar='<user>',
action='store',
help=_('Change to this user on startup (Requires root)'),
)
self.group.add_argument(
'-g',
'--group',
metavar='<group>',
action='store',
help=_('Change to this group on startup (Requires root)'),
)

View file

@ -9,11 +9,7 @@
# License.
# Written by Petru Paler
# Updated by Calum Lind to support both Python 2 and Python 3.
from sys import version_info
PY2 = version_info.major == 2
# Updated by Calum Lind to support Python 3.
class BTFailure(Exception):
@ -31,9 +27,9 @@ def decode_int(x, f):
f += 1
newf = x.index(END_DELIM, f)
n = int(x[f:newf])
if x[f:f+1] == b'-' and x[f+1:f+2] == b'0':
if x[f : f + 1] == b'-' and x[f + 1 : f + 2] == b'0':
raise ValueError
elif x[f:f+1] == b'0' and newf != f + 1:
elif x[f : f + 1] == b'0' and newf != f + 1:
raise ValueError
return (n, newf + 1)
@ -41,25 +37,25 @@ def decode_int(x, f):
def decode_string(x, f):
colon = x.index(BYTE_SEP, f)
n = int(x[f:colon])
if x[f:f+1] == b'0' and colon != f + 1:
if x[f : f + 1] == b'0' and colon != f + 1:
raise ValueError
colon += 1
return (x[colon:colon + n], colon + n)
return (x[colon : colon + n], colon + n)
def decode_list(x, f):
r, f = [], f + 1
while x[f:f+1] != END_DELIM:
v, f = decode_func[x[f:f+1]](x, f)
while x[f : f + 1] != END_DELIM:
v, f = decode_func[x[f : f + 1]](x, f)
r.append(v)
return (r, f + 1)
def decode_dict(x, f):
r, f = {}, f + 1
while x[f:f+1] != END_DELIM:
while x[f : f + 1] != END_DELIM:
k, f = decode_string(x, f)
r[k], f = decode_func[x[f:f+1]](x, f)
r[k], f = decode_func[x[f : f + 1]](x, f)
return (r, f + 1)
@ -81,15 +77,14 @@ decode_func[b'9'] = decode_string
def bdecode(x):
try:
r, l = decode_func[x[0:1]](x, 0)
except (IndexError, KeyError, ValueError):
r, __ = decode_func[x[0:1]](x, 0)
except (LookupError, TypeError, ValueError):
raise BTFailure('Not a valid bencoded string')
else:
return r
class Bencached(object):
class Bencached:
__slots__ = ['bencoded']
def __init__(self, s):
@ -109,7 +104,7 @@ def encode_bool(x, r):
def encode_string(x, r):
encode_string(x.encode('utf8'), r)
encode_bytes(x.encode('utf8'), r)
def encode_bytes(x, r):
@ -126,6 +121,10 @@ def encode_list(x, r):
def encode_dict(x, r):
r.append(DICT_DELIM)
for k, v in sorted(x.items()):
try:
k = k.encode('utf8')
except AttributeError:
pass
r.extend((str(len(k)).encode('utf8'), BYTE_SEP, k))
encode_func[type(v)](v, r)
r.append(END_DELIM)
@ -140,10 +139,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
encode_func[str] = encode_bytes
encode_func[unicode] = encode_string
def bencode(x):

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com>
#
@ -7,8 +6,6 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import logging
import traceback
from collections import defaultdict
@ -17,8 +14,6 @@ from twisted.internet import reactor
from twisted.internet.defer import DeferredList, fail, maybeDeferred, succeed
from twisted.internet.task import LoopingCall, deferLater
from deluge.common import PY2
log = logging.getLogger(__name__)
@ -27,15 +22,14 @@ class ComponentAlreadyRegistered(Exception):
class ComponentException(Exception):
def __init__(self, message, tb):
super(ComponentException, self).__init__(message)
super().__init__(message)
self.message = message
self.tb = tb
def __str__(self):
s = super(ComponentException, self).__str__()
return '%s\n%s' % (s, ''.join(self.tb))
s = super().__str__()
return '{}\n{}'.format(s, ''.join(self.tb))
def __eq__(self, other):
if isinstance(other, self.__class__):
@ -47,7 +41,7 @@ class ComponentException(Exception):
return not self.__eq__(other)
class Component(object):
class Component:
"""Component objects are singletons managed by the :class:`ComponentRegistry`.
When a new Component object is instantiated, it will be automatically
@ -65,11 +59,16 @@ class Component(object):
Deluge core.
**update()** - This method is called every 1 second by default while the
Componented is in a *Started* state. The interval can be
Component is in a *Started* state. The interval can be
specified during instantiation. The update() timer can be
paused by instructing the :class:`ComponentRegistry` to pause
this Component.
**pause()** - This method is called when the component is being paused.
**resume()** - This method is called when the component resumes from a Paused
state.
**shutdown()** - This method is called when the client is exiting. If the
Component is in a "Started" state when this is called, a
call to stop() will be issued prior to shutdown().
@ -86,13 +85,14 @@ class Component(object):
**Stopped** - The Component has either been stopped or has yet to be started.
**Stopping** - The Component has had it's stop method called, but it hasn't
**Stopping** - The Component has had its stop method called, but it hasn't
fully stopped yet.
**Paused** - The Component has had it's update timer stopped, but will
**Paused** - The Component has had its update timer stopped, but will
still be considered in a Started state.
"""
def __init__(self, name, interval=1, depend=None):
"""Initialize component.
@ -116,9 +116,8 @@ class Component(object):
_ComponentRegistry.deregister(self)
def _component_start_timer(self):
if hasattr(self, 'update'):
self._component_timer = LoopingCall(self.update)
self._component_timer.start(self._component_interval)
self._component_timer = LoopingCall(self.update)
self._component_timer.start(self._component_interval)
def _component_start(self):
def on_start(result):
@ -134,22 +133,23 @@ class Component(object):
return fail(result)
if self._component_state == 'Stopped':
if hasattr(self, 'start'):
self._component_state = 'Starting'
d = deferLater(reactor, 0, self.start)
d.addCallbacks(on_start, on_start_fail)
self._component_starting_deferred = d
else:
d = maybeDeferred(on_start, None)
self._component_state = 'Starting'
d = deferLater(reactor, 0, self.start)
d.addCallbacks(on_start, on_start_fail)
self._component_starting_deferred = d
elif self._component_state == 'Starting':
return self._component_starting_deferred
elif self._component_state == 'Started':
d = succeed(True)
else:
d = fail(ComponentException('Trying to start component "%s" but it is '
'not in a stopped state. Current state: %s' %
(self._component_name, self._component_state),
traceback.format_stack(limit=4)))
d = fail(
ComponentException(
'Trying to start component "%s" but it is '
'not in a stopped state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_stop(self):
@ -166,14 +166,11 @@ class Component(object):
return result
if self._component_state != 'Stopped' and self._component_state != 'Stopping':
if hasattr(self, 'stop'):
self._component_state = 'Stopping'
d = maybeDeferred(self.stop)
d.addCallback(on_stop)
d.addErrback(on_stop_fail)
self._component_stopping_deferred = d
else:
d = maybeDeferred(on_stop, None)
self._component_state = 'Stopping'
d = maybeDeferred(self.stop)
d.addCallback(on_stop)
d.addErrback(on_stop_fail)
self._component_stopping_deferred = d
if self._component_state == 'Stopping':
return self._component_stopping_deferred
@ -183,41 +180,47 @@ class Component(object):
def _component_pause(self):
def on_pause(result):
self._component_state = 'Paused'
if self._component_timer and self._component_timer.running:
self._component_timer.stop()
if self._component_state == 'Started':
if self._component_timer and self._component_timer.running:
d = maybeDeferred(self._component_timer.stop)
d.addCallback(on_pause)
else:
d = succeed(None)
d = maybeDeferred(self.pause)
d.addCallback(on_pause)
elif self._component_state == 'Paused':
d = succeed(None)
else:
d = fail(ComponentException('Trying to pause component "%s" but it is '
'not in a started state. Current state: %s' %
(self._component_name, self._component_state),
traceback.format_stack(limit=4)))
d = fail(
ComponentException(
'Trying to pause component "%s" but it is '
'not in a started state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_resume(self):
def on_resume(result):
self._component_state = 'Started'
self._component_start_timer()
if self._component_state == 'Paused':
d = maybeDeferred(self._component_start_timer)
d = maybeDeferred(self.resume)
d.addCallback(on_resume)
else:
d = fail(ComponentException('Trying to resume component "%s" but it is '
'not in a paused state. Current state: %s' %
(self._component_name, self._component_state),
traceback.format_stack(limit=4)))
d = fail(
ComponentException(
'Trying to resume component "%s" but it is '
'not in a paused state. Current state: %s'
% (self._component_name, self._component_state),
traceback.format_stack(limit=4),
)
)
return d
def _component_shutdown(self):
def on_stop(result):
if hasattr(self, 'shutdown'):
return maybeDeferred(self.shutdown)
return succeed(None)
return maybeDeferred(self.shutdown)
d = self._component_stop()
d.addCallback(on_stop)
@ -238,12 +241,19 @@ class Component(object):
def shutdown(self):
pass
def pause(self):
pass
class ComponentRegistry(object):
def resume(self):
pass
class ComponentRegistry:
"""The ComponentRegistry holds a list of currently registered :class:`Component` objects.
It is used to manage the Components by starting, stopping, pausing and shutting them down.
"""
def __init__(self):
self.components = {}
# Stores all of the components that are dependent on a particular component
@ -264,7 +274,9 @@ class ComponentRegistry(object):
"""
name = obj._component_name
if name in self.components:
raise ComponentAlreadyRegistered('Component already registered with name %s' % name)
raise ComponentAlreadyRegistered(
'Component already registered with name %s' % name
)
self.components[obj._component_name] = obj
if obj._component_depend:
@ -279,7 +291,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():
@ -289,6 +302,7 @@ class ComponentRegistry(object):
def on_stop(result, name):
# Component may have been removed, so pop to ensure it doesn't fail
self.components.pop(name, None)
return d.addCallback(on_stop, obj._component_name)
else:
return succeed(None)
@ -309,7 +323,7 @@ class ComponentRegistry(object):
# Start all the components if names is empty
if not names:
names = list(self.components)
elif isinstance(names, str if not PY2 else basestring):
elif isinstance(names, str):
names = [names]
def on_depends_started(result, name):
@ -343,7 +357,7 @@ class ComponentRegistry(object):
"""
if not names:
names = list(self.components)
elif isinstance(names, str if not PY2 else basestring):
elif isinstance(names, str):
names = [names]
def on_dependents_stopped(result, name):
@ -358,7 +372,9 @@ class ComponentRegistry(object):
if name in self.components:
if name in self.dependents:
# If other components depend on this component, stop them first
d = self.stop(self.dependents[name]).addCallback(on_dependents_stopped, name)
d = self.stop(self.dependents[name]).addCallback(
on_dependents_stopped, name
)
deferreds.append(d)
stopped_in_deferred.update(self.dependents[name])
else:
@ -381,7 +397,7 @@ class ComponentRegistry(object):
"""
if not names:
names = list(self.components)
elif isinstance(names, str if not PY2 else basestring):
elif isinstance(names, str):
names = [names]
deferreds = []
@ -407,7 +423,7 @@ class ComponentRegistry(object):
"""
if not names:
names = list(self.components)
elif isinstance(names, str if not PY2 else basestring):
elif isinstance(names, str):
names = [names]
deferreds = []
@ -428,8 +444,11 @@ class ComponentRegistry(object):
Deferred: Fired once all Components have been successfully shut down.
"""
def on_stopped(result):
return DeferredList([comp._component_shutdown() for comp in self.components.values()])
return DeferredList(
[comp._component_shutdown() for comp in list(self.components.values())]
)
return self.stop(list(self.components)).addCallback(on_stopped)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@ -39,71 +38,67 @@ 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 cPickle as pickle
import json
import logging
import os
import pickle
import shutil
from codecs import getwriter
from io import open
from tempfile import NamedTemporaryFile
from deluge.common import JSON_FORMAT, get_default_config_dir
log = logging.getLogger(__name__)
callLater = None # Necessary for the config tests
def prop(func):
"""Function decorator for defining property attributes
The decorated function is expected to return a dictionary
containing one or more of the following pairs:
fget - function for getting attribute value
fset - function for setting attribute value
fdel - function for deleting attribute
This can be conveniently constructed by the locals() builtin
function; see:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
"""
return property(doc=func.__doc__, **func())
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 []
for index, c in enumerate(s[offset:]):
if 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
class Config(object):
def cast_to_existing_type(value, old_value):
"""Attempt to convert new value type to match old value type"""
types_match = isinstance(old_value, (type(None), type(value)))
if value is not None and not types_match:
old_type = type(old_value)
# Skip convert to bytes since requires knowledge of encoding and value should
# be unicode anyway.
if old_type is bytes:
return value
return old_type(value)
return value
class Config:
"""This class is used to access/create/modify config files.
Args:
@ -113,18 +108,26 @@ class Config(object):
file_version (int): The file format for the default config values when creating
a fresh config. This value should be increased whenever a new migration function is
setup to convert old config files. (default: 1)
log_mask_funcs (dict): A dict of key:function, used to mask sensitive
key values (e.g. passwords) when logging is enabled.
"""
def __init__(self, filename, defaults=None, config_dir=None, file_version=1):
def __init__(
self,
filename,
defaults=None,
config_dir=None,
file_version=1,
log_mask_funcs=None,
):
self.__config = {}
self.__set_functions = {}
self.__change_callbacks = []
self.__log_mask_funcs = log_mask_funcs if log_mask_funcs else {}
# These hold the version numbers and they will be set when loaded
self.__version = {
'format': 1,
'file': file_version
}
self.__version = {'format': 1, 'file': file_version}
# This will get set with a reactor.callLater whenever a config option
# is set.
@ -132,7 +135,7 @@ class Config(object):
if defaults:
for key, value in defaults.items():
self.set_item(key, value)
self.set_item(key, value, default=True)
# Load the config from file in the config_dir
if config_dir:
@ -142,6 +145,12 @@ class Config(object):
self.load()
def callLater(self, period, func, *args, **kwargs): # noqa: N802 ignore camelCase
"""Wrapper around reactor.callLater for test purpose."""
from twisted.internet import reactor
return reactor.callLater(period, func, *args, **kwargs)
def __contains__(self, item):
return item in self.__config
@ -150,7 +159,7 @@ class Config(object):
return self.set_item(key, value)
def set_item(self, key, value):
def set_item(self, key, value, default=False):
"""Sets item 'key' to 'value' in the config dictionary.
Does not allow changing the item's type unless it is None.
@ -162,6 +171,8 @@ class Config(object):
key (str): Item to change to change.
value (any): The value to change item to, must be same type as what is
currently in the config.
default (optional, bool): When setting a default value skip func or save
callbacks.
Raises:
ValueError: Raised when the type of value is not the same as what is
@ -174,54 +185,54 @@ class Config(object):
5
"""
if key not in self.__config:
self.__config[key] = value
log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(value))
return
if isinstance(value, bytes):
value = value.decode()
if self.__config[key] == value:
return
# Do not allow the type to change unless it is None
if value is not None and not isinstance(
self.__config[key], type(None)) and not isinstance(self.__config[key], type(value)):
if key in self.__config:
try:
oldtype = type(self.__config[key])
value = oldtype(value)
value = cast_to_existing_type(value, self.__config[key])
except ValueError:
log.warning('Value Type "%s" invalid for key: %s', type(value), key)
raise
else:
if self.__config[key] == value:
return
if isinstance(value, bytes):
value.decode('utf8')
log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(value))
if log.isEnabledFor(logging.DEBUG):
if key in self.__log_mask_funcs:
value = self.__log_mask_funcs[key](value)
log.debug(
'Setting key "%s" to: %s (of type: %s)',
key,
value,
type(value),
)
self.__config[key] = value
global callLater
if callLater is None:
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
from twisted.internet.reactor import callLater # pylint: disable=redefined-outer-name
# Skip save or func callbacks if setting default value for keys
if default:
return
# Run the set_function for this key if any
for func in self.__set_functions.get(key, []):
self.callLater(0, func, key, value)
try:
for func in self.__set_functions[key]:
callLater(0, func, key, value)
except KeyError:
pass
try:
def do_change_callbacks(key, value):
for func in self.__change_callbacks:
func(key, value)
callLater(0, do_change_callbacks, key, value)
self.callLater(0, do_change_callbacks, key, value)
except Exception:
pass
# We set the save_timer for 5 seconds if not already set
if not self._save_timer or not self._save_timer.active():
self._save_timer = callLater(5, self.save)
self._save_timer = self.callLater(5, self.save)
def __getitem__(self, key):
"""See get_item """
"""See get_item"""
return self.get_item(key)
def get_item(self, key):
@ -294,14 +305,9 @@ class Config(object):
del self.__config[key]
global callLater
if callLater is None:
# Must import here and not at the top or it will throw ReactorAlreadyInstalledError
from twisted.internet.reactor import 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():
self._save_timer = callLater(5, self.save)
self._save_timer = self.callLater(5, self.save)
def register_change_callback(self, callback):
"""Registers a callback function for any changed value.
@ -347,7 +353,6 @@ class Config(object):
# Run the function now if apply_now is set
if apply_now:
function(key, self.__config[key])
return
def apply_all(self):
"""Calls all set functions.
@ -390,9 +395,9 @@ class Config(object):
filename = self.__config_file
try:
with open(filename, 'r', encoding='utf8') as _file:
with open(filename, encoding='utf8') as _file:
data = _file.read()
except IOError as ex:
except OSError as ex:
log.warning('Unable to open config file %s: %s', filename, ex)
return
@ -422,8 +427,25 @@ class Config(object):
log.exception(ex)
log.warning('Unable to load config file: %s', filename)
log.debug('Config %s version: %s.%s loaded: %s', filename,
self.__version['format'], self.__version['file'], self.__config)
if not log.isEnabledFor(logging.DEBUG):
return
config = self.__config
if self.__log_mask_funcs:
config = {
key: self.__log_mask_funcs[key](config[key])
if key in self.__log_mask_funcs
else config[key]
for key in config
}
log.debug(
'Config %s version: %s.%s loaded: %s',
filename,
self.__version['format'],
self.__version['file'],
config,
)
def save(self, filename=None):
"""Save configuration to disk.
@ -440,7 +462,7 @@ class Config(object):
# Check to see if the current config differs from the one on disk
# We will only write a new config file if there is a difference
try:
with open(filename, 'r', encoding='utf8') as _file:
with open(filename, encoding='utf8') as _file:
data = _file.read()
objects = find_json_objects(data)
start, end = objects[0]
@ -452,34 +474,40 @@ class Config(object):
if self._save_timer and self._save_timer.active():
self._save_timer.cancel()
return True
except (IOError, IndexError) as ex:
except (OSError, IndexError) as ex:
log.warning('Unable to open config file: %s because: %s', filename, ex)
# Save the new config and make sure it's written to disk
try:
log.debug('Saving new config file %s', filename + '.new')
with open(filename + '.new', 'wb') as _file:
with NamedTemporaryFile(
prefix=os.path.basename(filename) + '.', delete=False
) as _file:
filename_tmp = _file.name
log.debug('Saving new config file %s', filename_tmp)
json.dump(self.__version, getwriter('utf8')(_file), **JSON_FORMAT)
json.dump(self.__config, getwriter('utf8')(_file), **JSON_FORMAT)
_file.flush()
os.fsync(_file.fileno())
except IOError as ex:
except OSError as ex:
log.error('Error writing new config file: %s', ex)
return False
# Resolve symlinked config files before backing up and saving.
filename = os.path.realpath(filename)
# Make a backup of the old config
try:
log.debug('Backing up old config file to %s.bak', filename)
shutil.move(filename, filename + '.bak')
except IOError as ex:
except OSError as ex:
log.warning('Unable to backup old config: %s', ex)
# The new config file has been written successfully, so let's move it over
# the existing one.
try:
log.debug('Moving new config file %s to %s..', filename + '.new', filename)
shutil.move(filename + '.new', filename)
except IOError as ex:
log.debug('Moving new config file %s to %s', filename_tmp, filename)
shutil.move(filename_tmp, filename)
except OSError as ex:
log.error('Error moving new config file: %s', ex)
return False
else:
@ -505,16 +533,23 @@ class Config(object):
raise ValueError('output_version needs to be greater than input_range')
if self.__version['file'] not in input_range:
log.debug('File version %s is not in input_range %s, ignoring converter function..',
self.__version['file'], input_range)
log.debug(
'File version %s is not in input_range %s, ignoring converter function..',
self.__version['file'],
input_range,
)
return
try:
self.__config = func(self.__config)
except Exception as ex:
log.exception(ex)
log.error('There was an exception try to convert config file %s %s to %s',
self.__config_file, self.__version['file'], output_version)
log.error(
'There was an exception try to convert config file %s %s to %s',
self.__config_file,
self.__version['file'],
output_version,
)
raise ex
else:
self.__version['file'] = output_version
@ -524,12 +559,11 @@ class Config(object):
def config_file(self):
return self.__config_file
@prop
def config(): # pylint: disable=no-method-argument
@property
def config(self):
"""The config dictionary"""
def fget(self):
return self.__config
return self.__config
def fdel(self):
return self.save()
return locals()
@config.deleter
def config(self):
return self.save()

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@ -7,8 +6,6 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import logging
import os
@ -19,7 +16,7 @@ from deluge.config import Config
log = logging.getLogger(__name__)
class _ConfigManager(object):
class _ConfigManager:
def __init__(self):
log.debug('ConfigManager started..')
self.config_files = {}
@ -94,9 +91,12 @@ class _ConfigManager(object):
log.debug('Getting config: %s', config_file)
# Create the config object if not already created
if config_file not in self.config_files:
self.config_files[config_file] = Config(config_file, defaults,
config_dir=self.config_directory,
file_version=file_version)
self.config_files[config_file] = Config(
config_file,
defaults,
config_dir=self.config_directory,
file_version=file_version,
)
return self.config_files[config_file]
@ -106,7 +106,9 @@ _configmanager = _ConfigManager()
def ConfigManager(config, defaults=None, file_version=1): # NOQA: N802
return _configmanager.get_config(config, defaults=defaults, file_version=file_version)
return _configmanager.get_config(
config, defaults=defaults, file_version=file_version
)
def set_config_dir(directory):

215
deluge/conftest.py Normal file
View file

@ -0,0 +1,215 @@
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import asyncio
import tempfile
import warnings
from unittest.mock import Mock, patch
import pytest
import pytest_twisted
from twisted.internet import reactor
from twisted.internet.defer import Deferred, maybeDeferred
from twisted.internet.error import CannotListenError, ProcessTerminated
from twisted.python.failure import Failure
import deluge.component as _component
import deluge.configmanager
from deluge.common import get_localhost_auth
from deluge.tests import common
from deluge.ui.client import client as _client
DEFAULT_LISTEN_PORT = 58900
@pytest.fixture
def listen_port(request):
if request and 'daemon' in request.fixturenames:
try:
return request.getfixturevalue('daemon').listen_port
except Exception:
pass
return DEFAULT_LISTEN_PORT
@pytest.fixture
def mock_callback():
"""Returns a `Mock` object which can be registered as a callback to test against.
If callback was not called within `timeout` seconds, it will raise a TimeoutError.
The returned Mock instance will have a `deferred` attribute which will complete when the callback has been called.
"""
def reset(timeout=0.5, *args, **kwargs):
if mock.called:
original_reset_mock(*args, **kwargs)
if mock.deferred:
mock.deferred.cancel()
deferred = Deferred(canceller=lambda x: deferred.callback(None))
deferred.addTimeout(timeout, reactor)
mock.side_effect = lambda *args, **kw: deferred.callback((args, kw))
mock.deferred = deferred
mock = Mock()
mock.__qualname__ = 'mock'
original_reset_mock = mock.reset_mock
mock.reset_mock = reset
mock.reset_mock()
return mock
@pytest.fixture
def config_dir(tmp_path):
config_dir = tmp_path / 'config'
deluge.configmanager.set_config_dir(config_dir)
yield config_dir
@pytest_twisted.async_yield_fixture()
async def client(request, config_dir, monkeypatch, listen_port):
# monkeypatch.setattr(
# _client, 'connect', functools.partial(_client.connect, port=listen_port)
# )
try:
username, password = get_localhost_auth()
except Exception:
username, password = '', ''
await _client.connect(
'localhost',
port=listen_port,
username=username,
password=password,
)
yield _client
if _client.connected():
await _client.disconnect()
@pytest_twisted.async_yield_fixture
async def daemon(request, config_dir, tmp_path):
listen_port = DEFAULT_LISTEN_PORT
logfile = tmp_path / 'daemon.log'
if hasattr(request.cls, 'daemon_custom_script'):
custom_script = request.cls.daemon_custom_script
else:
custom_script = ''
for dummy in range(10):
try:
d, daemon = common.start_core(
listen_port=listen_port,
logfile=logfile,
timeout=5,
timeout_msg='Timeout!',
custom_script=custom_script,
print_stdout=True,
print_stderr=True,
config_directory=config_dir,
)
await d
except CannotListenError as ex:
exception_error = ex
listen_port += 1
except (KeyboardInterrupt, SystemExit):
raise
else:
break
else:
raise exception_error
daemon.listen_port = listen_port
yield daemon
try:
await daemon.kill()
except ProcessTerminated:
pass
@pytest.fixture(autouse=True)
def common_fixture(config_dir, request, monkeypatch, listen_port):
"""Adds some instance attributes to test classes for backwards compatibility with old testing."""
def fail(self, reason):
if isinstance(reason, Failure):
reason = reason.value
return pytest.fail(str(reason))
if request.instance:
request.instance.patch = monkeypatch.setattr
request.instance.config_dir = config_dir
request.instance.listen_port = listen_port
request.instance.id = lambda: request.node.name
request.cls.fail = fail
@pytest_twisted.async_yield_fixture(scope='function')
async def component():
"""Verify component registry is clean, and clean up after test."""
if len(_component._ComponentRegistry.components) != 0:
warnings.warn(
'The component._ComponentRegistry.components is not empty on test setup.\n'
'This is probably caused by another test that did not clean up after finishing!: %s'
% _component._ComponentRegistry.components
)
yield _component
await _component.shutdown()
_component._ComponentRegistry.components.clear()
_component._ComponentRegistry.dependents.clear()
@pytest_twisted.async_yield_fixture(scope='function')
async def base_fixture(common_fixture, component, request):
"""This fixture is autoused on all tests that subclass BaseTestCase"""
self = request.instance
if hasattr(self, 'set_up'):
try:
await maybeDeferred(self.set_up)
except Exception as exc:
warnings.warn('Error caught in test setup!\n%s' % exc)
pytest.fail('Error caught in test setup!\n%s' % exc)
yield
if hasattr(self, 'tear_down'):
try:
await maybeDeferred(self.tear_down)
except Exception as exc:
pytest.fail('Error caught in test teardown!\n%s' % exc)
@pytest.mark.usefixtures('base_fixture')
class BaseTestCase:
"""This is the base class that should be used for all test classes
that create classes that inherit from deluge.component.Component. It
ensures that the component registry has been cleaned up when tests
have finished.
"""
@pytest.fixture
def mock_mkstemp(tmp_path):
"""Return known tempfile location to verify file deleted"""
tmp_file = tempfile.mkstemp(dir=tmp_path)
with patch('tempfile.mkstemp', return_value=tmp_file):
yield tmp_file
def pytest_collection_modifyitems(session, config, items) -> None:
"""
Automatically runs async tests with pytest_twisted.ensureDeferred
"""
function_items = (item for item in items if isinstance(item, pytest.Function))
for function_item in function_items:
function = function_item.obj
if hasattr(function, '__func__'):
# methods need to be unwrapped.
function = function.__func__
if asyncio.iscoroutinefunction(function):
# This is how pytest_twisted marks ensureDeferred tests
setattr(function, '_pytest_twisted_mark', 'async_test')

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@ -15,11 +14,16 @@ This should typically only be used by the Core. Plugins should utilize the
`:mod:EventManager` for similar functionality.
"""
from __future__ import unicode_literals
import contextlib
import logging
import threading
import time
from collections import defaultdict
from functools import partial
from typing import Any, Callable
from twisted.internet import reactor
from twisted.internet import reactor, task, threads
import deluge.component as component
from deluge._libtorrent import lt
@ -30,68 +34,112 @@ log = logging.getLogger(__name__)
class AlertManager(component.Component):
"""AlertManager fetches and processes libtorrent alerts"""
def __init__(self):
log.debug('AlertManager init...')
component.Component.__init__(self, 'AlertManager', interval=0.3)
component.Component.__init__(self, 'AlertManager')
self.session = component.get('Core').session
# Increase the alert queue size so that alerts don't get lost.
self.alert_queue_size = 10000
self.set_alert_queue_size(self.alert_queue_size)
alert_mask = (lt.alert.category_t.error_notification |
lt.alert.category_t.port_mapping_notification |
lt.alert.category_t.storage_notification |
lt.alert.category_t.tracker_notification |
lt.alert.category_t.status_notification |
lt.alert.category_t.ip_block_notification |
lt.alert.category_t.performance_warning)
alert_mask = (
lt.alert.category_t.error_notification
| lt.alert.category_t.port_mapping_notification
| lt.alert.category_t.storage_notification
| lt.alert.category_t.tracker_notification
| lt.alert.category_t.status_notification
| lt.alert.category_t.ip_block_notification
| lt.alert.category_t.performance_warning
| lt.alert.category_t.file_progress_notification
)
self.session.apply_settings({'alert_mask': alert_mask})
# handlers is a dictionary of lists {"alert_type": [handler1,h2,..]}
self.handlers = {}
self.handlers = defaultdict(list)
self.handlers_timeout_secs = 2
self.delayed_calls = []
self._event = threading.Event()
def update(self):
self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()]
self.handle_alerts()
pass
def start(self):
thread = threading.Thread(
target=self.wait_for_alert_in_thread, name='alert-poller', daemon=True
)
thread.start()
self._event.set()
def stop(self):
self.cancel_delayed_calls()
def pause(self):
self._event.clear()
def resume(self):
self._event.set()
def wait_for_alert_in_thread(self):
while self._component_state not in ('Stopping', 'Stopped'):
if self.check_delayed_calls():
time.sleep(0.05)
continue
if self.session.wait_for_alert(1000) is None:
continue
if self._event.wait():
threads.blockingCallFromThread(reactor, self.maybe_handle_alerts)
def on_delayed_call_timeout(self, result, timeout, **kwargs):
log.warning('Alert handler was timed-out before being called %s', kwargs)
def cancel_delayed_calls(self):
"""Cancel all delayed handlers."""
for delayed_call in self.delayed_calls:
if delayed_call.active():
delayed_call.cancel()
delayed_call.cancel()
self.delayed_calls = []
def register_handler(self, alert_type, handler):
def check_delayed_calls(self) -> bool:
"""Returns True if any handler calls are delayed."""
self.delayed_calls = [dc for dc in self.delayed_calls if not dc.called]
return len(self.delayed_calls) > 0
def maybe_handle_alerts(self) -> None:
if self._component_state != 'Started':
return
self.handle_alerts()
def register_handler(self, alert_type: str, handler: Callable[[Any], None]) -> None:
"""
Registers a function that will be called when 'alert_type' is pop'd
in handle_alerts. The handler function should look like: handler(alert)
Where 'alert' is the actual alert object from libtorrent.
:param alert_type: str, this is string representation of the alert name
:param handler: func(alert), the function to be called when the alert is raised
Args:
alert_type: String representation of the libtorrent alert name.
Can be supplied with or without `_alert` suffix.
handler: Callback function when the alert is raised.
"""
if alert_type not in self.handlers:
# There is no entry for this alert type yet, so lets make it with an
# empty list.
self.handlers[alert_type] = []
if alert_type and alert_type.endswith('_alert'):
alert_type = alert_type[: -len('_alert')]
# Append the handler to the list in the handlers dictionary
self.handlers[alert_type].append(handler)
log.debug('Registered handler for alert %s', alert_type)
def deregister_handler(self, handler):
def deregister_handler(self, handler: Callable[[Any], None]):
"""
De-registers the `:param:handler` function from all alert types.
De-registers the `handler` function from all alert types.
:param handler: func, the handler function to deregister
Args:
handler: The handler function to deregister.
"""
# Iterate through all handlers and remove 'handler' where found
for (dummy_key, value) in self.handlers.items():
if handler in value:
# Handler is in this alert type list
value.remove(handler)
for alert_type_handlers in self.handlers.values():
with contextlib.suppress(ValueError):
alert_type_handlers.remove(handler)
def handle_alerts(self):
"""
@ -105,23 +153,42 @@ class AlertManager(component.Component):
if log.isEnabledFor(logging.DEBUG):
log.debug('Alerts queued: %s', num_alerts)
if num_alerts > 0.9 * self.alert_queue_size:
log.warning('Warning total alerts queued, %s, passes 90%% of queue size.', num_alerts)
log.warning(
'Warning total alerts queued, %s, passes 90%% of queue size.',
num_alerts,
)
# Loop through all alerts in the queue
for alert in alerts:
alert_type = type(alert).__name__
alert_type = alert.what()
# Display the alert message
if log.isEnabledFor(logging.DEBUG):
log.debug('%s: %s', alert_type, decode_bytes(alert.message()))
if alert_type not in self.handlers:
continue
# Call any handlers for this alert type
if alert_type in self.handlers:
for handler in self.handlers[alert_type]:
if log.isEnabledFor(logging.DEBUG):
log.debug('Handling alert: %s', alert_type)
self.delayed_calls.append(reactor.callLater(0, handler, alert))
for handler in self.handlers[alert_type]:
if log.isEnabledFor(logging.DEBUG):
log.debug('Handling alert: %s', alert_type)
d = task.deferLater(reactor, 0, handler, alert)
on_handler_timeout = partial(
self.on_delayed_call_timeout,
handler=handler.__qualname__,
alert_type=alert_type,
)
d.addTimeout(
self.handlers_timeout_secs,
reactor,
onTimeoutCancel=on_handler_timeout,
)
self.delayed_calls.append(d)
def set_alert_queue_size(self, queue_size):
"""Sets the maximum size of the libtorrent alert queue"""
log.info('Alert Queue Size set to %s', queue_size)
self.alert_queue_size = queue_size
component.get('Core').apply_session_setting('alert_queue_size', self.alert_queue_size)
component.get('Core').apply_session_setting(
'alert_queue_size', self.alert_queue_size
)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@ -8,17 +7,20 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import logging
import os
import shutil
from io import open
import deluge.component as component
import deluge.configmanager as configmanager
from deluge.common import (AUTH_LEVEL_ADMIN, AUTH_LEVEL_DEFAULT, AUTH_LEVEL_NONE, AUTH_LEVEL_NORMAL,
AUTH_LEVEL_READONLY, create_localclient_account)
from deluge.common import (
AUTH_LEVEL_ADMIN,
AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_NONE,
AUTH_LEVEL_NORMAL,
AUTH_LEVEL_READONLY,
create_localclient_account,
)
from deluge.error import AuthenticationRequired, AuthManagerError, BadLoginError
log = logging.getLogger(__name__)
@ -26,13 +28,14 @@ log = logging.getLogger(__name__)
AUTH_LEVELS_MAPPING = {
'NONE': AUTH_LEVEL_NONE,
'READONLY': AUTH_LEVEL_READONLY,
'DEFAULT': AUTH_LEVEL_NORMAL,
'NORMAL': AUTH_LEVEL_DEFAULT,
'ADMIN': AUTH_LEVEL_ADMIN}
'DEFAULT': AUTH_LEVEL_DEFAULT,
'NORMAL': AUTH_LEVEL_NORMAL,
'ADMIN': AUTH_LEVEL_ADMIN,
}
AUTH_LEVELS_MAPPING_REVERSE = {v: k for k, v in AUTH_LEVELS_MAPPING.items()}
class Account(object):
class Account:
__slots__ = ('username', 'password', 'authlevel')
def __init__(self, username, password, authlevel):
@ -45,12 +48,14 @@ class Account(object):
'username': self.username,
'password': self.password,
'authlevel': AUTH_LEVELS_MAPPING_REVERSE[self.authlevel],
'authlevel_int': self.authlevel
'authlevel_int': self.authlevel,
}
def __repr__(self):
return ('<Account username="%(username)s" authlevel=%(authlevel)s>' %
{'username': self.username, 'authlevel': self.authlevel})
return '<Account username="{username}" authlevel={authlevel}>'.format(
username=self.username,
authlevel=self.authlevel,
)
class AuthManager(component.Component):
@ -92,7 +97,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.
"""
@ -129,8 +134,9 @@ class AuthManager(component.Component):
if authlevel not in AUTH_LEVELS_MAPPING:
raise AuthManagerError('Invalid auth level: %s' % authlevel)
try:
self.__auth[username] = Account(username, password,
AUTH_LEVELS_MAPPING[authlevel])
self.__auth[username] = Account(
username, password, AUTH_LEVELS_MAPPING[authlevel]
)
self.write_auth_file()
return True
except Exception as ex:
@ -174,18 +180,21 @@ class AuthManager(component.Component):
if os.path.isfile(filepath):
log.debug('Creating backup of %s at: %s', filename, filepath_bak)
shutil.copy2(filepath, filepath_bak)
except IOError as ex:
except OSError as ex:
log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex)
else:
log.info('Saving the %s at: %s', filename, filepath)
try:
with open(filepath_tmp, 'w', encoding='utf8') as _file:
for account in self.__auth.values():
_file.write('%(username)s:%(password)s:%(authlevel_int)s\n' % account.data())
_file.write(
'%(username)s:%(password)s:%(authlevel_int)s\n'
% account.data()
)
_file.flush()
os.fsync(_file.fileno())
shutil.move(filepath_tmp, filepath)
except IOError as ex:
except OSError as ex:
log.error('Unable to save %s: %s', filename, ex)
if os.path.isfile(filepath_bak):
log.info('Restoring backup of %s from: %s', filename, filepath_bak)
@ -214,9 +223,9 @@ class AuthManager(component.Component):
for _filepath in (auth_file, auth_file_bak):
log.info('Opening %s for load: %s', filename, _filepath)
try:
with open(_filepath, 'r', encoding='utf8') as _file:
with open(_filepath, encoding='utf8') as _file:
file_data = _file.readlines()
except IOError as ex:
except OSError as ex:
log.warning('Unable to load %s: %s', _filepath, ex)
file_data = []
else:
@ -232,8 +241,12 @@ class AuthManager(component.Component):
lsplit = line.split(':')
if len(lsplit) == 2:
username, password = lsplit
log.warning('Your auth entry for %s contains no auth level, '
'using AUTH_LEVEL_DEFAULT(%s)..', username, AUTH_LEVEL_DEFAULT)
log.warning(
'Your auth entry for %s contains no auth level, '
'using AUTH_LEVEL_DEFAULT(%s)..',
username,
AUTH_LEVEL_DEFAULT,
)
if username == 'localclient':
authlevel = AUTH_LEVEL_ADMIN
else:
@ -254,7 +267,10 @@ class AuthManager(component.Component):
try:
authlevel = AUTH_LEVELS_MAPPING[authlevel]
except KeyError:
log.error('Your auth file is malformed: %r is not a valid auth level', authlevel)
log.error(
'Your auth file is malformed: %r is not a valid auth level',
authlevel,
)
continue
self.__auth[username] = Account(username, password, authlevel)

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@ -8,7 +7,6 @@
#
"""The Deluge daemon"""
from __future__ import unicode_literals
import logging
import os
@ -44,8 +42,8 @@ def is_daemon_running(pid_file):
try:
with open(pid_file) as _file:
pid, port = [int(x) for x in _file.readline().strip().split(';')]
except (EnvironmentError, ValueError):
pid, port = (int(x) for x in _file.readline().strip().split(';'))
except (OSError, ValueError):
return False
if is_process_running(pid):
@ -53,7 +51,7 @@ def is_daemon_running(pid_file):
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
_socket.connect(('127.0.0.1', port))
except socket.error:
except OSError:
# Can't connect, so pid is not a deluged process.
return False
else:
@ -62,43 +60,62 @@ def is_daemon_running(pid_file):
return True
class Daemon(object):
class Daemon:
"""The Deluge Daemon class"""
def __init__(self, listen_interface=None, interface=None, port=None, standalone=False,
read_only_config_keys=None):
def __init__(
self,
listen_interface=None,
outgoing_interface=None,
interface=None,
port=None,
standalone=False,
read_only_config_keys=None,
):
"""
Args:
listen_interface (str, optional): The IP address to listen to bittorrent connections on.
interface (str, optional): The IP address the daemon will listen for UI connections on.
port (int, optional): The port the daemon will listen for UI connections on.
standalone (bool, optional): If True the client is in Standalone mode otherwise, if
False, start the daemon as separate process.
read_only_config_keys (list of str, optional): A list of config keys that will not be
altered by core.set_config() RPC method.
listen_interface (str, optional): The IP address to listen to
BitTorrent connections on.
outgoing_interface (str, optional): The network interface name or
IP address to open outgoing BitTorrent connections on.
interface (str, optional): The IP address the daemon will
listen for UI connections on.
port (int, optional): The port the daemon will listen for UI
connections on.
standalone (bool, optional): If True the client is in Standalone
mode otherwise, if False, start the daemon as separate process.
read_only_config_keys (list of str, optional): A list of config
keys that will not be altered by core.set_config() RPC method.
"""
self.standalone = standalone
self.pid_file = get_config_dir('deluged.pid')
log.info('Deluge daemon %s', get_version())
if is_daemon_running(self.pid_file):
raise DaemonRunningError('Deluge daemon already running with this config directory!')
raise DaemonRunningError(
'Deluge daemon already running with this config directory!'
)
# Twisted catches signals to terminate, so just have it call the shutdown method.
reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown)
# Catch some Windows specific signals
if windows_check():
def win_handler(ctrl_type):
"""Handle the Windows shutdown or close events."""
log.debug('windows handler ctrl_type: %s', ctrl_type)
if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
self._shutdown()
return 1
SetConsoleCtrlHandler(win_handler)
# Start the core as a thread and join it until it's done
self.core = Core(listen_interface=listen_interface,
read_only_config_keys=read_only_config_keys)
self.core = Core(
listen_interface=listen_interface,
outgoing_interface=outgoing_interface,
read_only_config_keys=read_only_config_keys,
)
if port is None:
port = self.core.config['daemon_port']
@ -112,10 +129,16 @@ class Daemon(object):
port=port,
allow_remote=self.core.config['allow_remote'],
listen=not standalone,
interface=interface
interface=interface,
)
log.debug('Listening to UI on: %s:%s and bittorrent on: %s', interface, port, listen_interface)
log.debug(
'Listening to UI on: %s:%s and bittorrent on: %s Making connections out on: %s',
interface,
port,
listen_interface,
outgoing_interface,
)
def start(self):
# Register the daemon and the core RPCs
@ -131,7 +154,7 @@ class Daemon(object):
pid = os.getpid()
log.debug('Storing pid %s & port %s in: %s', pid, self.port, self.pid_file)
with open(self.pid_file, 'w') as _file:
_file.write('%s;%s\n' % (pid, self.port))
_file.write(f'{pid};{self.port}\n')
component.start()
@ -157,6 +180,11 @@ class Daemon(object):
"""Returns a list of the exported methods."""
return self.rpcserver.get_method_list()
@export()
def get_version(self):
"""Returns the daemon version"""
return get_version()
@export(1)
def authorized_call(self, rpc):
"""Determines if session auth_level is authorized to call RPC.
@ -170,4 +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)
)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me>
@ -7,30 +6,61 @@
# 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
from twisted.internet.error import CannotListenError
from deluge.argparserbase import ArgParserBase
from deluge.common import run_profiled
from deluge.configmanager import get_config_dir
from deluge.ui.baseargparser import BaseArgParser
from deluge.ui.translations_util import set_dummy_trans
from deluge.i18n import setup_mock_translation
def add_daemon_options(parser):
group = parser.add_argument_group(_('Daemon Options'))
group.add_argument('-u', '--ui-interface', metavar='<ip-addr>', action='store',
help=_('IP address to listen for UI connections'))
group.add_argument('-p', '--port', metavar='<port>', action='store', type=int,
help=_('Port to listen for UI connections on'))
group.add_argument('-i', '--interface', metavar='<ip-addr>', dest='listen_interface', action='store',
help=_('IP address to listen for BitTorrent connections'))
group.add_argument('--read-only-config-keys', metavar='<comma-separated-keys>', action='store',
help=_('Config keys to be unmodified by `set_config` RPC'), type=str, default='')
group.add_argument(
'-u',
'--ui-interface',
metavar='<ip-addr>',
action='store',
help=_('IP address to listen for UI connections'),
)
group.add_argument(
'-p',
'--port',
metavar='<port>',
action='store',
type=int,
help=_('Port to listen for UI connections on'),
)
group.add_argument(
'-i',
'--interface',
metavar='<ip-addr>',
dest='listen_interface',
action='store',
help=_('IP address to listen for BitTorrent connections'),
)
group.add_argument(
'-o',
'--outgoing-interface',
metavar='<interface>',
dest='outgoing_interface',
action='store',
help=_(
'The network interface name or IP address for outgoing BitTorrent connections.'
),
)
group.add_argument(
'--read-only-config-keys',
metavar='<comma-separated-keys>',
action='store',
help=_('Config keys to be unmodified by `set_config` RPC'),
type=str,
default='',
)
parser.add_process_arg_group()
@ -45,20 +75,23 @@ def start_daemon(skip_start=False):
deluge.core.daemon.Daemon: A new daemon object
"""
set_dummy_trans(warn_msg=True)
setup_mock_translation()
# Setup the argument parser
parser = BaseArgParser()
parser = ArgParserBase()
add_daemon_options(parser)
options = parser.parse_args()
# Check for any daemons running with this same config
from deluge.core.daemon import is_daemon_running
pid_file = get_config_dir('deluged.pid')
if is_daemon_running(pid_file):
print('Cannot run multiple daemons with same config directory.\n'
'If you believe this is an error, force starting by deleting: %s' % pid_file)
print(
'Cannot run multiple daemons with same config directory.\n'
'If you believe this is an error, force starting by deleting: %s' % pid_file
)
sys.exit(1)
log = getLogger(__name__)
@ -72,18 +105,25 @@ def start_daemon(skip_start=False):
def run_daemon(options):
try:
from deluge.core.daemon import Daemon
daemon = Daemon(listen_interface=options.listen_interface,
interface=options.ui_interface,
port=options.port,
read_only_config_keys=options.read_only_config_keys.split(','))
daemon = Daemon(
listen_interface=options.listen_interface,
outgoing_interface=options.outgoing_interface,
interface=options.ui_interface,
port=options.port,
read_only_config_keys=options.read_only_config_keys.split(','),
)
if skip_start:
return daemon
else:
daemon.start()
except CannotListenError as ex:
log.error('Cannot start deluged, listen port in use.\n'
' Check for other running daemons or services using this port: %s:%s',
ex.interface, ex.port)
log.error(
'Cannot start deluged, listen port in use.\n'
' Check for other running daemons or services using this port: %s:%s',
ex.interface,
ex.port,
)
sys.exit(1)
except Exception as ex:
log.error('Unable to start deluged: %s', ex)
@ -95,4 +135,6 @@ def start_daemon(skip_start=False):
if options.pidfile:
os.remove(options.pidfile)
return run_profiled(run_daemon, options, output_file=options.profile, do_profile=options.profile)
return run_profiled(
run_daemon, options, output_file=options.profile, do_profile=options.profile
)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@ -7,8 +6,6 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import logging
import deluge.component as component
@ -36,7 +33,12 @@ class EventManager(component.Component):
try:
handler(*event.args)
except Exception as ex:
log.error('Event handler %s failed in %s with exception %s', event.name, handler, ex)
log.error(
'Event handler %s failed in %s with exception %s',
event.name,
handler,
ex,
)
def register_event_handler(self, event, handler):
"""

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@ -7,12 +6,10 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import logging
import deluge.component as component
from deluge.common import PY2, TORRENT_STATE
from deluge.common import TORRENT_STATE
log = logging.getLogger(__name__)
@ -98,9 +95,8 @@ def tracker_error_filter(torrent_ids, values):
class FilterManager(component.Component):
"""FilterManager
"""FilterManager"""
"""
def __init__(self, core):
component.Component.__init__(self, 'FilterManager')
log.debug('FilterManager init..')
@ -115,12 +111,14 @@ class FilterManager(component.Component):
def _init_tracker_tree():
return {'Error': 0}
self.register_tree_field('tracker_host', _init_tracker_tree)
self.register_filter('tracker_host', tracker_error_filter)
def _init_users_tree():
return {'': 0}
self.register_tree_field('owner', _init_users_tree)
def filter_torrent_ids(self, filter_dict):
@ -133,7 +131,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, str if not PY2 else basestring):
if isinstance(value, str):
filter_dict[key] = [value]
# Optimized filter for id
@ -162,19 +160,25 @@ class FilterManager(component.Component):
return torrent_ids
# Registered filters
for field, values in filter_dict.items():
for field, values in list(filter_dict.items()):
if field in self.registered_filters:
# Filters out doubles
torrent_ids = list(set(self.registered_filters[field](torrent_ids, values)))
torrent_ids = list(
set(self.registered_filters[field](torrent_ids, values))
)
del filter_dict[field]
if not filter_dict:
return torrent_ids
torrent_keys, plugin_keys = self.torrents.separate_keys(list(filter_dict), torrent_ids)
torrent_keys, plugin_keys = self.torrents.separate_keys(
list(filter_dict), torrent_ids
)
# Leftover filter arguments, default filter on status fields.
for torrent_id in list(torrent_ids):
status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys)
status = self.core.create_torrent_status(
torrent_id, torrent_keys, plugin_keys
)
for field, values in filter_dict.items():
if field in status and status[field] in values:
continue
@ -194,17 +198,21 @@ class FilterManager(component.Component):
tree_keys.remove(cat)
torrent_keys, plugin_keys = self.torrents.separate_keys(tree_keys, torrent_ids)
items = dict((field, self.tree_fields[field]()) for field in tree_keys)
items = {field: self.tree_fields[field]() for field in tree_keys}
for torrent_id in list(torrent_ids):
status = self.core.create_torrent_status(torrent_id, torrent_keys, plugin_keys) # status={key:value}
status = self.core.create_torrent_status(
torrent_id, torrent_keys, plugin_keys
) # status={key:value}
for field in tree_keys:
value = status[field]
items[field][value] = items[field].get(value, 0) + 1
if 'tracker_host' in items:
items['tracker_host']['All'] = len(torrent_ids)
items['tracker_host']['Error'] = len(tracker_error_filter(torrent_ids, ('Error',)))
items['tracker_host']['Error'] = len(
tracker_error_filter(torrent_ids, ('Error',))
)
if not show_zero_hits:
for cat in ['state', 'owner', 'tracker_host']:
@ -215,7 +223,7 @@ class FilterManager(component.Component):
sorted_items = {field: sorted(items[field].items()) for field in tree_keys}
if 'state' in tree_keys:
sorted_items['state'].sort(self._sort_state_items)
sorted_items['state'].sort(key=self._sort_state_item)
return sorted_items
@ -224,7 +232,9 @@ class FilterManager(component.Component):
init_state['All'] = len(self.torrents.get_torrent_list())
for state in TORRENT_STATE:
init_state[state] = 0
init_state['Active'] = len(self.filter_state_active(self.torrents.get_torrent_list()))
init_state['Active'] = len(
self.filter_state_active(self.torrents.get_torrent_list())
)
return init_state
def register_filter(self, filter_id, filter_func, filter_value=None):
@ -242,7 +252,9 @@ class FilterManager(component.Component):
def filter_state_active(self, torrent_ids):
for torrent_id in list(torrent_ids):
status = self.torrents[torrent_id].get_status(['download_payload_rate', 'upload_payload_rate'])
status = self.torrents[torrent_id].get_status(
['download_payload_rate', 'upload_payload_rate']
)
if status['download_payload_rate'] or status['upload_payload_rate']:
pass
else:
@ -251,18 +263,12 @@ class FilterManager(component.Component):
def _hide_state_items(self, state_items):
"""For hide(show)-zero hits"""
for (value, count) in state_items.items():
for value, count in list(state_items.items()):
if value != 'All' and count == 0:
del state_items[value]
def _sort_state_items(self, x, y):
if x[0] in STATE_SORT:
ix = STATE_SORT.index(x[0])
else:
ix = 99
if y[0] in STATE_SORT:
iy = STATE_SORT.index(y[0])
else:
iy = 99
return ix - iy
def _sort_state_item(self, item):
try:
return STATE_SORT.index(item[0])
except ValueError:
return 99

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@ -9,7 +8,6 @@
"""PluginManager for Core"""
from __future__ import unicode_literals
import logging
@ -33,7 +31,8 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon
# Call the PluginManagerBase constructor
deluge.pluginmanagerbase.PluginManagerBase.__init__(
self, 'core.conf', 'deluge.plugin.core')
self, 'core.conf', 'deluge.plugin.core'
)
def start(self):
# Enable plugins that are enabled in the config
@ -76,6 +75,7 @@ class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Compon
if name not in self.plugins:
component.get('EventManager').emit(PluginDisabledEvent(name))
return result
d.addBoth(on_disable_plugin)
return d

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2010 Andrew Resch <andrewresch@gmail.com>
#
@ -8,13 +7,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
@ -24,17 +23,14 @@ import deluge.configmanager
from deluge._libtorrent import lt
from deluge.event import ConfigValueChangedEvent
GeoIP = None
try:
import GeoIP
from GeoIP import GeoIP
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
try:
from pygeoip import GeoIP
except ImportError:
pass
log = logging.getLogger(__name__)
@ -47,6 +43,7 @@ DEFAULT_PREFS = {
'download_location': deluge.common.get_default_download_dir(),
'listen_ports': [6881, 6891],
'listen_interface': '',
'outgoing_interface': '',
'random_port': True,
'listen_random_port': None,
'listen_use_sys_port': False,
@ -71,8 +68,11 @@ DEFAULT_PREFS = {
'max_upload_speed': -1.0,
'max_download_speed': -1.0,
'max_upload_slots_global': 4,
'max_half_open_connections': (lambda: deluge.common.windows_check() and
(lambda: deluge.common.vista_check() and 4 or 8)() or 50)(),
'max_half_open_connections': (
lambda: deluge.common.windows_check()
and (lambda: deluge.common.vista_check() and 4 or 8)()
or 50
)(),
'max_connections_per_second': 20,
'ignore_limits_on_local_network': True,
'max_connections_per_torrent': -1,
@ -122,7 +122,7 @@ DEFAULT_PREFS = {
'cache_expiry': 60,
'auto_manage_prefer_seeds': False,
'shared': False,
'super_seeding': False
'super_seeding': False,
}
@ -131,7 +131,9 @@ class PreferencesManager(component.Component):
component.Component.__init__(self, 'PreferencesManager')
self.config = deluge.configmanager.ConfigManager('core.conf', DEFAULT_PREFS)
if 'proxies' in self.config:
log.warning('Updating config file for proxy, using "peer" values to fill new "proxy" setting')
log.warning(
'Updating config file for proxy, using "peer" values to fill new "proxy" setting'
)
self.config['proxy'].update(self.config['proxies']['peer'])
log.warning('New proxy config is: %s', self.config['proxy'])
del self.config['proxies']
@ -187,28 +189,47 @@ class PreferencesManager(component.Component):
def _on_set_listen_interface(self, key, value):
self.__set_listen_on()
def _on_set_outgoing_interface(self, key, value):
"""Set interface name or IP address for outgoing BitTorrent connections."""
value = value.strip() if value else ''
self.core.apply_session_settings({'outgoing_interfaces': value})
def _on_set_random_port(self, key, value):
self.__set_listen_on()
def __set_listen_on(self):
""" Set the ports and interface address to listen for incoming connections on."""
"""Set the ports and interface address to listen for incoming connections on."""
if self.config['random_port']:
if not self.config['listen_random_port']:
self.config['listen_random_port'] = random.randrange(49152, 65525)
listen_ports = [self.config['listen_random_port']] * 2 # use single port range
listen_ports = [
self.config['listen_random_port']
] * 2 # use single port range
else:
self.config['listen_random_port'] = None
listen_ports = self.config['listen_ports']
interface = str(self.config['listen_interface'].strip())
interface = interface if interface else '0.0.0.0'
if self.config['listen_interface']:
interface = self.config['listen_interface'].strip()
else:
interface = '0.0.0.0'
log.debug('Listen Interface: %s, Ports: %s with use_sys_port: %s',
interface, listen_ports, self.config['listen_use_sys_port'])
interfaces = ['%s:%s' % (interface, port) for port in range(listen_ports[0], listen_ports[1]+1)]
log.debug(
'Listen Interface: %s, Ports: %s with use_sys_port: %s',
interface,
listen_ports,
self.config['listen_use_sys_port'],
)
interfaces = [
f'{interface}:{port}'
for port in range(listen_ports[0], listen_ports[1] + 1)
]
self.core.apply_session_settings(
{'listen_system_port_fallback': self.config['listen_use_sys_port'],
'listen_interfaces': ''.join(interfaces)})
{
'listen_system_port_fallback': self.config['listen_use_sys_port'],
'listen_interfaces': ','.join(interfaces),
}
)
def _on_set_outgoing_ports(self, key, value):
self.__set_outgoing_ports()
@ -217,14 +238,22 @@ class PreferencesManager(component.Component):
self.__set_outgoing_ports()
def __set_outgoing_ports(self):
port = 0 if self.config['random_outgoing_ports'] else self.config['outgoing_ports'][0]
port = (
0
if self.config['random_outgoing_ports']
else self.config['outgoing_ports'][0]
)
if port:
num_ports = self.config['outgoing_ports'][1] - self.config['outgoing_ports'][0]
num_ports = (
self.config['outgoing_ports'][1] - self.config['outgoing_ports'][0]
)
num_ports = num_ports if num_ports > 1 else 5
else:
num_ports = 0
log.debug('Outgoing port set to %s with range: %s', port, num_ports)
self.core.apply_session_settings({'outgoing_port': port, 'num_outgoing_ports': num_ports})
self.core.apply_session_settings(
{'outgoing_port': port, 'num_outgoing_ports': num_ports}
)
def _on_set_peer_tos(self, key, value):
try:
@ -233,8 +262,21 @@ class PreferencesManager(component.Component):
log.error('Invalid tos byte: %s', ex)
def _on_set_dht(self, key, value):
dht_bootstraps = 'router.bittorrent.com:6881,router.utorrent.com:6881,router.bitcomet.com:6881'
self.core.apply_session_settings({'dht_bootstrap_nodes': dht_bootstraps, 'enable_dht': value})
lt_bootstraps = self.core.session.get_settings()['dht_bootstrap_nodes']
# Update list of lt bootstraps, using set to remove duplicates.
dht_bootstraps = set(
lt_bootstraps.split(',')
+ [
'router.bittorrent.com:6881',
'router.utorrent.com:6881',
'router.bitcomet.com:6881',
'dht.transmissionbt.com:6881',
'dht.aelitis.com:6881',
]
)
self.core.apply_session_settings(
{'dht_bootstrap_nodes': ','.join(dht_bootstraps), 'enable_dht': value}
)
def _on_set_upnp(self, key, value):
self.core.apply_session_setting('enable_upnp', value)
@ -260,12 +302,21 @@ class PreferencesManager(component.Component):
def _on_set_encryption(self, key, value):
# Convert Deluge enc_level values to libtorrent enc_level values.
pe_enc_level = {0: lt.enc_level.plaintext, 1: lt.enc_level.rc4, 2: lt.enc_level.both}
pe_enc_level = {
0: lt.enc_level.plaintext,
1: lt.enc_level.rc4,
2: lt.enc_level.both,
}
self.core.apply_session_settings(
{'out_enc_policy': lt.enc_policy(self.config['enc_out_policy']),
'in_enc_policy': lt.enc_policy(self.config['enc_in_policy']),
'allowed_enc_level': lt.enc_level(pe_enc_level[self.config['enc_level']]),
'prefer_rc4': True})
{
'out_enc_policy': lt.enc_policy(self.config['enc_out_policy']),
'in_enc_policy': lt.enc_policy(self.config['enc_in_policy']),
'allowed_enc_level': lt.enc_level(
pe_enc_level[self.config['enc_level']]
),
'prefer_rc4': True,
}
)
def _on_set_max_connections_global(self, key, value):
self.core.apply_session_setting('connections_limit', value)
@ -327,20 +378,29 @@ class PreferencesManager(component.Component):
def run(self):
import time
now = time.time()
# check if we've done this within the last week or never
if (now - self.config['info_sent']) >= (60 * 60 * 24 * 7):
try:
url = 'http://deluge-torrent.org/stats_get.php?processor=' + \
platform.machine() + '&python=' + platform.python_version() \
+ '&deluge=' + deluge.common.get_version() \
+ '&os=' + platform.system() \
+ '&plugins=' + quote_plus(':'.join(self.config['enabled_plugins']))
url = (
'http://deluge-torrent.org/stats_get.php?processor='
+ platform.machine()
+ '&python='
+ platform.python_version()
+ '&deluge='
+ deluge.common.get_version()
+ '&os='
+ platform.system()
+ '&plugins='
+ quote_plus(':'.join(self.config['enabled_plugins']))
)
urlopen(url)
except IOError as ex:
except OSError as ex:
log.debug('Network error while trying to send info: %s', ex)
else:
self.config['info_sent'] = now
if value:
SendInfoThread(self.config).start()
@ -352,7 +412,8 @@ class PreferencesManager(component.Component):
self.new_release_timer.stop()
# Set a timer to check for a new release every 3 days
self.new_release_timer = LoopingCall(
self._on_set_new_release_check, 'new_release_check', True)
self._on_set_new_release_check, 'new_release_check', True
)
self.new_release_timer.start(72 * 60 * 60, False)
else:
if self.new_release_timer and self.new_release_timer.running:
@ -361,31 +422,34 @@ class PreferencesManager(component.Component):
def _on_set_proxy(self, key, value):
# Initialise with type none and blank hostnames.
proxy_settings = {
'proxy_type': lt.proxy_type.none,
'proxy_type': lt.proxy_type_t.none,
'i2p_hostname': '',
'proxy_hostname': '',
'proxy_hostnames': value['proxy_hostnames'],
'proxy_peer_connections': value['proxy_peer_connections'],
'proxy_tracker_connections': value['proxy_tracker_connections'],
'force_proxy': value['force_proxy'],
'anonymous_mode': value['anonymous_mode']
'anonymous_mode': value['anonymous_mode'],
}
if value['type'] == lt.proxy_type.i2p_proxy:
proxy_settings.update({
'proxy_type': lt.proxy_type.i2p_proxy,
'i2p_hostname': value['hostname'],
'i2p_port': value['port'],
})
elif value['type'] != lt.proxy_type.none:
proxy_settings.update({
'proxy_type': value['type'],
'proxy_hostname': value['hostname'],
'proxy_port': value['port'],
'proxy_username': value['username'],
'proxy_password': value['password'],
})
if value['type'] == lt.proxy_type_t.i2p_proxy:
proxy_settings.update(
{
'proxy_type': lt.proxy_type_t.i2p_proxy,
'i2p_hostname': value['hostname'],
'i2p_port': value['port'],
}
)
elif value['type'] != lt.proxy_type_t.none:
proxy_settings.update(
{
'proxy_type': value['type'],
'proxy_hostname': value['hostname'],
'proxy_port': value['port'],
'proxy_username': value['username'],
'proxy_password': value['password'],
}
)
self.core.apply_session_settings(proxy_settings)
@ -396,9 +460,9 @@ class PreferencesManager(component.Component):
# Load the GeoIP DB for country look-ups if available
if os.path.exists(geoipdb_path):
try:
self.core.geoip_instance = GeoIP.open(geoipdb_path, GeoIP.GEOIP_STANDARD)
except AttributeError:
log.warning('GeoIP Unavailable')
self.core.geoip_instance = GeoIP(geoipdb_path, 0)
except Exception as ex:
log.warning('GeoIP Unavailable: %s', ex)
else:
log.warning('Unable to find GeoIP database file: %s', geoipdb_path)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008,2009 Andrew Resch <andrewresch@gmail.com>
#
@ -8,24 +7,33 @@
#
"""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 typing import Callable, TypeVar, overload
from OpenSSL import SSL, crypto
from twisted.internet import defer, reactor
from twisted.internet.protocol import Factory, connectionDone
import deluge.component as component
import deluge.configmanager
from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AUTH_LEVEL_DEFAULT, AUTH_LEVEL_NONE
from deluge.error import DelugeError, IncompatibleClient, NotAuthorizedError, WrappedException, _ClientSideRecreateError
from deluge.core.authmanager import (
AUTH_LEVEL_ADMIN,
AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_NONE,
)
from deluge.crypto_utils import check_ssl_keys, get_context_factory
from deluge.error import (
DelugeError,
IncompatibleClient,
NotAuthorizedError,
WrappedException,
_ClientSideRecreateError,
)
from deluge.event import ClientDisconnectedEvent
from deluge.transfer import DelugeTransferProtocol
@ -35,6 +43,16 @@ RPC_EVENT = 3
log = logging.getLogger(__name__)
TCallable = TypeVar('TCallable', bound=Callable)
@overload
def export(func: TCallable) -> TCallable: ...
@overload
def export(auth_level: int) -> Callable[[TCallable], TCallable]: ...
def export(auth_level=AUTH_LEVEL_DEFAULT):
"""
@ -47,13 +65,23 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
:type auth_level: int
"""
def wrap(func, *args, **kwargs):
func._rpcserver_export = True
func._rpcserver_auth_level = auth_level
doc = func.__doc__
func.__doc__ = '**RPC Exported Function** (*Auth Level: %s*)\n\n' % auth_level
if doc:
func.__doc__ += doc
rpc_text = '**RPC exported method** (*Auth level: %s*)' % auth_level
# Append the RPC text while ensuring correct docstring formatting.
if func.__doc__:
if func.__doc__.endswith(' '):
indent = func.__doc__.split('\n')[-1]
func.__doc__ += f'\n{indent}'
else:
func.__doc__ += '\n\n'
func.__doc__ += rpc_text
else:
func.__doc__ = rpc_text
return func
@ -91,25 +119,9 @@ def format_request(call):
return s
class ServerContextFactory(object):
def getContext(self): # NOQA: N802
"""
Create an SSL context.
This loads the servers cert/private key SSL files for use with the
SSL transport.
"""
ssl_dir = deluge.configmanager.get_config_dir('ssl')
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
ctx.use_certificate_file(os.path.join(ssl_dir, 'daemon.cert'))
ctx.use_privatekey_file(os.path.join(ssl_dir, 'daemon.pkey'))
return ctx
class DelugeRPCProtocol(DelugeTransferProtocol):
def __init__(self):
super(DelugeRPCProtocol, self).__init__()
super().__init__()
# namedtuple subclass with auth_level, username for the connected session.
self.AuthLevel = namedtuple('SessionAuthlevel', 'auth_level, username')
@ -134,8 +146,10 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
for call in request:
if len(call) != 4:
log.debug('Received invalid rpc request: number of items '
'in request is %s', len(call))
log.debug(
'Received invalid rpc request: number of items ' 'in request is %s',
len(call),
)
continue
# log.debug('RPCRequest: %s', format_request(call))
reactor.callLater(0, self.dispatch, *call)
@ -152,7 +166,7 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
try:
self.transfer_message(data)
except Exception as ex:
log.warn('Error occurred when sending message: %s.', ex)
log.warning('Error occurred when sending message: %s.', ex)
log.exception(ex)
raise
@ -161,11 +175,11 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
This method is called when a new client connects.
"""
peer = self.transport.getPeer()
log.info('Deluge Client connection made from: %s:%s',
peer.host, peer.port)
log.info('Deluge Client connection made from: %s:%s', peer.host, peer.port)
# Set the initial auth level of this session to AUTH_LEVEL_NONE
self.factory.authorized_sessions[
self.transport.sessionno] = self.AuthLevel(AUTH_LEVEL_NONE, '')
self.factory.authorized_sessions[self.transport.sessionno] = self.AuthLevel(
AUTH_LEVEL_NONE, ''
)
def connectionLost(self, reason=connectionDone): # NOQA: N802
"""
@ -184,7 +198,9 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
del self.factory.interested_events[self.transport.sessionno]
if self.factory.state == 'running':
component.get('EventManager').emit(ClientDisconnectedEvent(self.factory.session_id))
component.get('EventManager').emit(
ClientDisconnectedEvent(self.factory.session_id)
)
log.info('Deluge client disconnected: %s', reason.value)
def valid_session(self):
@ -206,32 +222,42 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
:type kwargs: dict
"""
def send_error():
"""
Sends an error response with the contents of the exception that was raised.
"""
exceptionType, exceptionValue, dummy_exceptionTraceback = sys.exc_info()
exc_type, exc_value, dummy_exc_trace = sys.exc_info()
formated_tb = traceback.format_exc()
try:
self.sendData((
RPC_ERROR,
request_id,
exceptionType.__name__,
exceptionValue._args,
exceptionValue._kwargs,
formated_tb
))
self.sendData(
(
RPC_ERROR,
request_id,
exc_type.__name__,
exc_value._args,
exc_value._kwargs,
formated_tb,
)
)
except AttributeError:
# This is not a deluge exception (object has no attribute '_args), let's wrap it
log.warning('An exception occurred while sending RPC_ERROR to '
'client. Wrapping it and resending. Error to '
'send(causing exception goes next):\n%s', formated_tb)
log.warning(
'An exception occurred while sending RPC_ERROR to '
'client. Wrapping it and resending. Error to '
'send(causing exception goes next):\n%s',
formated_tb,
)
try:
raise WrappedException(str(exceptionValue), exceptionType.__name__, formated_tb)
raise WrappedException(
str(exc_value), exc_type.__name__, formated_tb
)
except WrappedException:
send_error()
except Exception as ex:
log.error('An exception occurred while sending RPC_ERROR to client: %s', ex)
log.error(
'An exception occurred while sending RPC_ERROR to client: %s', ex
)
if method == 'daemon.info':
# This is a special case and used in the initial connection process
@ -247,8 +273,9 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
raise IncompatibleClient(deluge.common.get_version())
ret = component.get('AuthManager').authorize(*args, **kwargs)
if ret:
self.factory.authorized_sessions[
self.transport.sessionno] = self.AuthLevel(ret, args[0])
self.factory.authorized_sessions[self.transport.sessionno] = (
self.AuthLevel(ret, args[0])
)
self.factory.session_protocols[self.transport.sessionno] = self
except Exception as ex:
send_error()
@ -290,11 +317,15 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
log.debug('RPC dispatch %s', method)
try:
method_auth_requirement = self.factory.methods[method]._rpcserver_auth_level
auth_level = self.factory.authorized_sessions[self.transport.sessionno].auth_level
auth_level = self.factory.authorized_sessions[
self.transport.sessionno
].auth_level
if auth_level < method_auth_requirement:
# This session is not allowed to call this method
log.debug('Session %s is attempting an unauthorized method call!',
self.transport.sessionno)
log.debug(
'Session %s is attempting an unauthorized method call!',
self.transport.sessionno,
)
raise NotAuthorizedError(auth_level, method_auth_requirement)
# Set the session_id in the factory so that methods can know
# which session is calling it.
@ -310,6 +341,7 @@ class DelugeRPCProtocol(DelugeTransferProtocol):
# Check if the return value is a deferred, since we'll need to
# wait for it to fire before sending the RPC_RESPONSE
if isinstance(ret, defer.Deferred):
def on_success(result):
try:
self.sendData((RPC_RESPONSE, request_id, result))
@ -379,8 +411,13 @@ class RPCServer(component.Component):
# Check for SSL keys and generate some if needed
check_ssl_keys()
cert = os.path.join(deluge.configmanager.get_config_dir('ssl'), 'daemon.cert')
pkey = os.path.join(deluge.configmanager.get_config_dir('ssl'), 'daemon.pkey')
try:
reactor.listenSSL(port, self.factory, ServerContextFactory(), interface=hostname)
reactor.listenSSL(
port, self.factory, get_context_factory(cert, pkey), interface=hostname
)
except Exception as ex:
log.debug('Daemon already running or port not available.: %s', ex)
raise
@ -507,8 +544,8 @@ class RPCServer(component.Component):
:type event: :class:`deluge.event.DelugeEvent`
"""
log.debug('intevents: %s', self.factory.interested_events)
# Find sessions interested in this event
for session_id, interest in self.factory.interested_events.items():
# Use copy of `interested_events` since it can mutate while iterating.
for session_id, interest in self.factory.interested_events.copy().items():
if event.name in interest:
log.debug('Emit Event: %s %s', event.name, event.args)
# This session is interested so send a RPC_EVENT
@ -526,73 +563,35 @@ class RPCServer(component.Component):
:type event: :class:`deluge.event.DelugeEvent`
"""
if not self.is_session_valid(session_id):
log.debug('Session ID %s is not valid. Not sending event "%s".', session_id, event.name)
log.debug(
'Session ID %s is not valid. Not sending event "%s".',
session_id,
event.name,
)
return
if session_id not in self.factory.interested_events:
log.debug('Session ID %s is not interested in any events. Not sending event "%s".',
session_id, event.name)
log.debug(
'Session ID %s is not interested in any events. Not sending event "%s".',
session_id,
event.name,
)
return
if event.name not in self.factory.interested_events[session_id]:
log.debug('Session ID %s is not interested in event "%s". Not sending it.', session_id, event.name)
log.debug(
'Session ID %s is not interested in event "%s". Not sending it.',
session_id,
event.name,
)
return
log.debug('Sending event "%s" with args "%s" to session id "%s".',
event.name, event.args, session_id)
self.factory.session_protocols[session_id].sendData((RPC_EVENT, event.name, event.args))
log.debug(
'Sending event "%s" with args "%s" to session id "%s".',
event.name,
event.args,
session_id,
)
self.factory.session_protocols[session_id].sendData(
(RPC_EVENT, event.name, event.args)
)
def stop(self):
self.factory.state = 'stopping'
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)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@ -14,11 +13,12 @@ Attributes:
"""
from __future__ import division, unicode_literals
import logging
import os
import socket
import time
from typing import Optional
from urllib.parse import urlparse
from twisted.internet.defer import Deferred, DeferredList
@ -28,19 +28,11 @@ from deluge.common import decode_bytes
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.decorators import deprecated
from deluge.event import TorrentFolderRenamedEvent, TorrentStateChangedEvent, 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
from deluge.event import (
TorrentFolderRenamedEvent,
TorrentStateChangedEvent,
TorrentTrackerStatusEvent,
)
log = logging.getLogger(__name__)
@ -52,7 +44,7 @@ LT_TORRENT_STATE_MAP = {
'finished': 'Seeding',
'seeding': 'Seeding',
'allocating': 'Allocating',
'checking_resume_data': 'Checking'
'checking_resume_data': 'Checking',
}
@ -65,6 +57,7 @@ def sanitize_filepath(filepath, folder=False):
Args:
folder (bool): A trailing slash is appended to the returned filepath.
"""
def clean_filename(filename):
"""Strips whitespace and discards dotted filenames"""
filename = filename.strip()
@ -89,7 +82,7 @@ def convert_lt_files(files):
"""Indexes and decodes files from libtorrent get_files().
Args:
files (list): The libtorrent torrent files.
files (file_storage): The libtorrent torrent files.
Returns:
list of dict: The files.
@ -104,18 +97,20 @@ def convert_lt_files(files):
}
"""
filelist = []
for index, _file in enumerate(files):
for index in range(files.num_files()):
try:
file_path = _file.path.decode('utf8')
file_path = files.file_path(index).decode('utf8')
except AttributeError:
file_path = _file.path
file_path = files.file_path(index)
filelist.append({
'index': index,
'path': file_path.replace('\\', '/'),
'size': _file.size,
'offset': _file.offset
})
filelist.append(
{
'index': index,
'path': file_path.replace('\\', '/'),
'size': files.file_size(index),
'offset': files.file_offset(index),
}
)
return filelist
@ -128,7 +123,7 @@ class TorrentOptions(dict):
auto_managed (bool): Set torrent to auto managed mode, i.e. will be started or queued automatically.
download_location (str): The path for the torrent data to be stored while downloading.
file_priorities (list of int): The priority for files in torrent, range is [0..7] however
only [0, 1, 5, 7] are normally used and correspond to [Do Not Download, Normal, High, Highest]
only [0, 1, 4, 7] are normally used and correspond to [Skip, Low, Normal, High]
mapped_files (dict): A mapping of the renamed filenames in 'index:filename' pairs.
max_connections (int): Sets maximum number of connections this torrent will open.
This must be at least 2. The default is unlimited (-1).
@ -152,8 +147,9 @@ class TorrentOptions(dict):
stop_ratio (float): The seeding ratio to stop (or remove) the torrent at.
super_seeding (bool): Enable super seeding/initial seeding.
"""
def __init__(self):
super(TorrentOptions, self).__init__()
super().__init__()
config = ConfigManager('core.conf').config
options_conf_map = {
'add_paused': 'add_paused',
@ -172,7 +168,7 @@ class TorrentOptions(dict):
'shared': 'shared',
'stop_at_ratio': 'stop_seed_at_ratio',
'stop_ratio': 'stop_seed_ratio',
'super_seeding': 'super_seeding'
'super_seeding': 'super_seeding',
}
for opt_k, conf_k in options_conf_map.items():
self[opt_k] = config[conf_k]
@ -183,14 +179,14 @@ class TorrentOptions(dict):
self['seed_mode'] = False
class TorrentError(object):
class TorrentError:
def __init__(self, error_message, was_paused=False, restart_to_resume=False):
self.error_message = error_message
self.was_paused = was_paused
self.restart_to_resume = restart_to_resume
class Torrent(object):
class Torrent:
"""Torrent holds information about torrents added to the libtorrent session.
Args:
@ -198,12 +194,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.
@ -227,6 +223,7 @@ class Torrent(object):
we can re-pause it after its done if necessary
forced_error (TorrentError): Keep track if we have forced this torrent to be in Error state.
"""
def __init__(self, handle, options, state=None, filename=None, magnet=None):
self.torrent_id = str(handle.info_hash())
if log.isEnabledFor(logging.DEBUG):
@ -237,12 +234,12 @@ class Torrent(object):
self.rpcserver = component.get('RPCServer')
self.handle = handle
self.handle.resolve_countries(True)
self.magnet = magnet
self.status = self.handle.status()
self._status: Optional['lt.torrent_status'] = None
self._status_last_update: float = 0.0
self.torrent_info = self.handle.get_torrent_info()
self.torrent_info = self.handle.torrent_file()
self.has_metadata = self.status.has_metadata
self.options = TorrentOptions()
@ -258,6 +255,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
@ -270,7 +270,6 @@ class Torrent(object):
self.prev_status = {}
self.waiting_on_folder_rename = []
self.update_status(self.handle.status())
self._create_status_funcs()
self.set_options(self.options)
self.update_state()
@ -278,6 +277,18 @@ class Torrent(object):
if log.isEnabledFor(logging.DEBUG):
log.debug('Torrent object created.')
def _set_handle_flags(self, flag: lt.torrent_flags, set_flag: bool):
"""set or unset a flag to the lt handle
Args:
flag (lt.torrent_flags): the flag to set/unset
set_flag (bool): True for setting the flag, False for unsetting it
"""
if set_flag:
self.handle.set_flags(flag)
else:
self.handle.unset_flags(flag)
def on_metadata_received(self):
"""Process the metadata received alert for this torrent"""
self.has_metadata = True
@ -296,7 +307,9 @@ class Torrent(object):
# Skip set_prioritize_first_last if set_file_priorities is in options as it also calls the method.
if 'file_priorities' in options and 'prioritize_first_last_pieces' in options:
self.options['prioritize_first_last_pieces'] = options.pop('prioritize_first_last_pieces')
self.options['prioritize_first_last_pieces'] = options.pop(
'prioritize_first_last_pieces'
)
for key, value in options.items():
if key in self.options:
@ -360,7 +373,7 @@ class Torrent(object):
"""Sets maximum download speed for this torrent.
Args:
m_up_speed (float): Maximum download speed in KiB/s.
m_down_speed (float): Maximum download speed in KiB/s.
"""
self.options['max_download_speed'] = m_down_speed
if m_down_speed < 0:
@ -392,7 +405,7 @@ class Torrent(object):
return
# A list of priorities for each piece in the torrent
priorities = self.handle.piece_priorities()
priorities = self.handle.get_piece_priorities()
def get_file_piece(idx, byte_offset):
return self.torrent_info.map_file(idx, byte_offset, 0).piece
@ -408,20 +421,27 @@ class Torrent(object):
# Set the pieces in first and last ranges to priority 7
# if they are not marked as do not download
priorities[first_start:first_end] = [p and 7 for p in priorities[first_start:first_end]]
priorities[last_start:last_end] = [p and 7 for p in priorities[last_start:last_end]]
priorities[first_start:first_end] = [
p and 7 for p in priorities[first_start:first_end]
]
priorities[last_start:last_end] = [
p and 7 for p in priorities[last_start:last_end]
]
# 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._set_handle_flags(
flag=lt.torrent_flags.sequential_download,
set_flag=sequential,
)
def set_auto_managed(self, auto_managed):
"""Set auto managed mode, i.e. will be started or queued automatically.
@ -431,7 +451,10 @@ class Torrent(object):
"""
self.options['auto_managed'] = auto_managed
if not (self.status.paused and not self.status.auto_managed):
self.handle.auto_managed(auto_managed)
self._set_handle_flags(
flag=lt.torrent_flags.auto_managed,
set_flag=auto_managed,
)
self.update_state()
def set_super_seeding(self, super_seeding):
@ -440,11 +463,11 @@ class Torrent(object):
Args:
super_seeding (bool): Enable super seeding.
"""
if self.status.is_seeding:
self.options['super_seeding'] = super_seeding
self.handle.super_seeding(super_seeding)
else:
self.options['super_seeding'] = False
self.options['super_seeding'] = super_seeding
self._set_handle_flags(
flag=lt.torrent_flags.super_seeding,
set_flag=super_seeding,
)
def set_stop_ratio(self, stop_ratio):
"""The seeding ratio to stop (or remove) the torrent at.
@ -493,32 +516,35 @@ class Torrent(object):
Args:
file_priorities (list of int): List of file priorities.
"""
if not self.has_metadata:
return
if log.isEnabledFor(logging.DEBUG):
log.debug('Setting %s file priorities to: %s', self.torrent_id, file_priorities)
log.debug(
'Setting %s file priorities to: %s', self.torrent_id, file_priorities
)
if (self.handle.has_metadata() and file_priorities and
len(file_priorities) == len(self.get_files())):
if file_priorities and len(file_priorities) == len(self.get_files()):
self.handle.prioritize_files(file_priorities)
else:
log.debug('Unable to set new file priorities.')
file_priorities = self.handle.file_priorities()
file_priorities = self.handle.get_file_priorities()
if 0 in self.options['file_priorities']:
# Previously marked a file 'Do Not Download' so check if changed any 0's to >0.
# Previously marked a file 'skip' so check for any 0's now >0.
for index, priority in enumerate(self.options['file_priorities']):
if priority == 0 and file_priorities[index] > 0:
# Changed 'Do Not Download' to a download priority so update state.
# Changed priority from skip to download so update state.
self.is_finished = False
self.update_state()
break
# Ensure stored options are in sync in case file_priorities were faulty (old state?).
self.options['file_priorities'] = self.handle.file_priorities()
# Store the priorities.
self.options['file_priorities'] = file_priorities
# Set the first/last priorities if needed.
if self.options['prioritize_first_last_pieces']:
self.set_prioritize_first_last_pieces(self.options['prioritize_first_last_pieces'])
self.set_prioritize_first_last_pieces(True)
@deprecated
def set_save_path(self, download_location):
@ -552,7 +578,7 @@ class Torrent(object):
trackers (list of dicts): A list of trackers.
"""
if trackers is None:
self.trackers = [tracker for tracker in self.handle.trackers()]
self.trackers = list(self.handle.trackers())
self.tracker_host = None
return
@ -594,11 +620,16 @@ class Torrent(object):
if self.tracker_status != status:
self.tracker_status = status
component.get('EventManager').emit(TorrentTrackerStatusEvent(self.torrent_id, self.tracker_status))
component.get('EventManager').emit(
TorrentTrackerStatusEvent(self.torrent_id, self.tracker_status)
)
def merge_trackers(self, torrent_info):
"""Merges new trackers in torrent_info into torrent"""
log.info('Adding any new trackers to torrent (%s) already in session...', self.torrent_id)
log.info(
'Adding any new trackers to torrent (%s) already in session...',
self.torrent_id,
)
if not torrent_info:
return
# Don't merge trackers if either torrent has private flag set.
@ -612,7 +643,7 @@ class Torrent(object):
def update_state(self):
"""Updates the state, based on libtorrent's torrent state"""
status = self.handle.status()
status = self.get_lt_status()
session_paused = component.get('Core').session.is_paused()
old_state = self.state
self.set_status_message()
@ -624,7 +655,10 @@ class Torrent(object):
elif status_error:
self.state = 'Error'
# auto-manage status will be reverted upon resuming.
self.handle.auto_managed(False)
self._set_handle_flags(
flag=lt.torrent_flags.auto_managed,
set_flag=False,
)
self.set_status_message(decode_bytes(status_error))
elif status.moving_storage:
self.state = 'Moving'
@ -636,13 +670,23 @@ class Torrent(object):
self.state = LT_TORRENT_STATE_MAP.get(str(status.state), str(status.state))
if self.state != old_state:
component.get('EventManager').emit(TorrentStateChangedEvent(self.torrent_id, self.state))
component.get('EventManager').emit(
TorrentStateChangedEvent(self.torrent_id, self.state)
)
if log.isEnabledFor(logging.DEBUG):
log.debug('State from lt was: %s | Session is paused: %s\nTorrent state set from "%s" to "%s" (%s)',
'error' if status_error else status.state, session_paused, old_state, self.state, self.torrent_id)
log.debug(
'State from lt was: %s | Session is paused: %s\nTorrent state set from "%s" to "%s" (%s)',
'error' if status_error else status.state,
session_paused,
old_state,
self.state,
self.torrent_id,
)
if self.forced_error:
log.debug('Torrent Error state message: %s', self.forced_error.error_message)
log.debug(
'Torrent Error state message: %s', self.forced_error.error_message
)
def set_status_message(self, message=None):
"""Sets the torrent status message.
@ -667,8 +711,11 @@ class Torrent(object):
restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting
session can resume.
"""
status = self.handle.status()
self.handle.auto_managed(False)
status = self.get_lt_status()
self._set_handle_flags(
flag=lt.torrent_flags.auto_managed,
set_flag=False,
)
self.forced_error = TorrentError(message, status.paused, restart_to_resume)
if not status.paused:
self.handle.pause()
@ -682,7 +729,10 @@ class Torrent(object):
log.error('Restart deluge to clear this torrent error')
if not self.forced_error.was_paused and self.options['auto_managed']:
self.handle.auto_managed(True)
self._set_handle_flags(
flag=lt.torrent_flags.auto_managed,
set_flag=True,
)
self.forced_error = None
self.set_status_message('OK')
if update_state:
@ -697,16 +747,23 @@ class Torrent(object):
"""
status = self.status
eta = 0
if self.is_finished and self.options['stop_at_ratio'] and status.upload_payload_rate:
if (
self.is_finished
and self.options['stop_at_ratio']
and status.upload_payload_rate
):
# We're a seed, so calculate the time to the 'stop_share_ratio'
eta = ((status.all_time_download * self.options['stop_ratio']) -
status.all_time_upload) // status.upload_payload_rate
eta = (
int(status.all_time_download * self.options['stop_ratio'])
- status.all_time_upload
) // status.upload_payload_rate
elif status.download_payload_rate:
left = status.total_wanted - status.total_wanted_done
if left > 0:
eta = left // status.download_payload_rate
return eta
# Limit to 1 year, avoid excessive values and prevent GTK int overflow.
return eta if eta < 31557600 else -1
def get_ratio(self):
"""Get the ratio of upload/download for this torrent.
@ -774,27 +831,37 @@ 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(peer.ip[0])
country = component.get('Core').geoip_instance.country_code_by_addr(
peer.ip[0]
)
except AttributeError:
country = ''
else:
try:
country = ''.join([char if char.isalpha() else ' ' for char in country])
country = ''.join(
[char if char.isalpha() else ' ' for char in country]
)
except TypeError:
country = ''
ret.append({
'client': client,
'country': country,
'down_speed': peer.payload_down_speed,
'ip': '%s:%s' % (peer.ip[0], peer.ip[1]),
'progress': peer.progress,
'seed': peer.flags & peer.seed,
'up_speed': peer.payload_up_speed,
})
ret.append(
{
'client': client,
'country': country,
'down_speed': peer.payload_down_speed,
'ip': f'{peer.ip[0]}:{peer.ip[1]}',
'progress': peer.progress,
'seed': peer.flags & peer.seed,
'up_speed': peer.payload_up_speed,
}
)
return ret
@ -808,7 +875,7 @@ class Torrent(object):
def get_file_priorities(self):
"""Return the file priorities"""
if not self.handle.has_metadata():
if not self.handle.status().has_metadata:
return []
if not self.options['file_priorities']:
@ -825,8 +892,19 @@ class Torrent(object):
"""
if not self.has_metadata:
return []
return [progress / _file.size if _file.size else 0.0 for progress, _file in
zip(self.handle.file_progress(), self.torrent_info.files())]
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):
"""Get the hostname of the currently connected tracker.
@ -846,11 +924,11 @@ class Torrent(object):
if tracker:
url = urlparse(tracker.replace('udp://', 'http://'))
if hasattr(url, 'hostname'):
host = (url.hostname or 'DHT')
host = url.hostname or 'DHT'
# Check if hostname is an IP address and just return it if that's the case
try:
socket.inet_aton(host)
except socket.error:
except OSError:
pass
else:
# This is an IP address because an exception wasn't raised
@ -867,7 +945,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):
@ -881,14 +959,18 @@ class Torrent(object):
str: the name of the torrent.
"""
if not self.options['name']:
handle_name = self.handle.name()
if handle_name:
name = decode_bytes(handle_name)
else:
name = self.torrent_id
if self.options['name']:
return self.options['name']
if self.has_metadata:
# Use the top-level folder as torrent name.
filename = decode_bytes(self.torrent_info.files().file_path(0))
name = filename.replace('\\', '/', 1).split('/', 1)[0]
else:
name = self.options['name']
name = decode_bytes(self.handle.status().name)
if not name:
name = self.torrent_id
return name
@ -937,12 +1019,14 @@ 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
"""
if update:
self.update_status(self.handle.status())
self.get_lt_status()
if all_keys:
keys = list(self.status_funcs)
@ -972,13 +1056,35 @@ class Torrent(object):
return status_dict
def update_status(self, status):
def get_lt_status(self) -> 'lt.torrent_status':
"""Get the torrent status fresh, not from cache.
This should be used when a guaranteed fresh status is needed rather than
`torrent.handle.status()` because it will update the cache as well.
"""
self.status = self.handle.status()
return self.status
@property
def status(self) -> 'lt.torrent_status':
"""Cached copy of the libtorrent status for this torrent.
If it has not been updated within the last five seconds, it will be
automatically refreshed.
"""
if self._status_last_update < (time.time() - 5):
self.status = self.handle.status()
return self._status
@status.setter
def status(self, status: 'lt.torrent_status') -> None:
"""Updates the cached status.
Args:
status (libtorrent.torrent_status): a libtorrent torrent status
status: a libtorrent torrent status
"""
self.status = status
self._status = status
self._status_last_update = time.time()
def _create_status_funcs(self):
"""Creates the functions for getting torrent status"""
@ -987,7 +1093,9 @@ class Torrent(object):
'seeding_time': lambda: self.status.seeding_time,
'finished_time': lambda: self.status.finished_time,
'all_time_download': lambda: self.status.all_time_download,
'storage_mode': lambda: self.status.storage_mode.name.split('_')[2], # sparse or allocate
'storage_mode': lambda: self.status.storage_mode.name.split('_')[
2
], # sparse or allocate
'distributed_copies': lambda: max(0.0, self.status.distributed_copies),
'download_payload_rate': lambda: self.status.download_payload_rate,
'file_priorities': self.get_file_priorities,
@ -1000,8 +1108,12 @@ class Torrent(object):
'max_upload_slots': lambda: self.options['max_upload_slots'],
'max_upload_speed': lambda: self.options['max_upload_speed'],
'message': lambda: self.statusmsg,
'move_on_completed_path': lambda: self.options['move_completed_path'], # Deprecated: move_completed_path
'move_on_completed': lambda: self.options['move_completed'], # Deprecated: Use move_completed
'move_on_completed_path': lambda: self.options[
'move_completed_path'
], # Deprecated: move_completed_path
'move_on_completed': lambda: self.options[
'move_completed'
], # Deprecated: Use move_completed
'move_completed_path': lambda: self.options['move_completed_path'],
'move_completed': lambda: self.options['move_completed'],
'next_announce': lambda: self.status.next_announce.seconds,
@ -1009,17 +1121,25 @@ class Torrent(object):
'num_seeds': lambda: self.status.num_seeds,
'owner': lambda: self.options['owner'],
'paused': lambda: self.status.paused,
'prioritize_first_last': lambda: self.options['prioritize_first_last_pieces'],
'prioritize_first_last': lambda: self.options[
'prioritize_first_last_pieces'
],
# Deprecated: Use prioritize_first_last_pieces
'prioritize_first_last_pieces': lambda: self.options['prioritize_first_last_pieces'],
'prioritize_first_last_pieces': lambda: self.options[
'prioritize_first_last_pieces'
],
'sequential_download': lambda: self.options['sequential_download'],
'progress': self.get_progress,
'shared': lambda: self.options['shared'],
'remove_at_ratio': lambda: self.options['remove_at_ratio'],
'save_path': lambda: self.options['download_location'], # Deprecated: Use download_location
'save_path': lambda: self.options[
'download_location'
], # Deprecated: Use download_location
'download_location': lambda: self.options['download_location'],
'seeds_peers_ratio': lambda: -1.0 if self.status.num_incomplete == 0 else ( # Use -1.0 to signify infinity
self.status.num_complete / self.status.num_incomplete),
'seeds_peers_ratio': lambda: -1.0
if self.status.num_incomplete == 0
# Use -1.0 to signify infinity
else (self.status.num_complete / self.status.num_incomplete),
'seed_rank': lambda: self.status.seed_rank,
'state': lambda: self.state,
'stop_at_ratio': lambda: self.options['stop_at_ratio'],
@ -1032,19 +1152,32 @@ class Torrent(object):
'total_seeds': lambda: self.status.num_complete,
'total_uploaded': lambda: self.status.all_time_upload,
'total_wanted': lambda: self.status.total_wanted,
'total_remaining': lambda: self.status.total_wanted - self.status.total_wanted_done,
'total_remaining': lambda: self.status.total_wanted
- self.status.total_wanted_done,
'tracker': lambda: self.status.current_tracker,
'tracker_host': self.get_tracker_host,
'trackers': lambda: self.trackers,
'tracker_status': lambda: self.tracker_status,
'upload_payload_rate': lambda: self.status.upload_payload_rate,
'comment': lambda: decode_bytes(self.torrent_info.comment()) if self.has_metadata else '',
'creator': lambda: decode_bytes(self.torrent_info.creator()) if self.has_metadata else '',
'num_files': lambda: self.torrent_info.num_files() if self.has_metadata else 0,
'num_pieces': lambda: self.torrent_info.num_pieces() if self.has_metadata else 0,
'piece_length': lambda: self.torrent_info.piece_length() if self.has_metadata else 0,
'comment': lambda: decode_bytes(self.torrent_info.comment())
if self.has_metadata
else '',
'creator': lambda: decode_bytes(self.torrent_info.creator())
if self.has_metadata
else '',
'num_files': lambda: self.torrent_info.num_files()
if self.has_metadata
else 0,
'num_pieces': lambda: self.torrent_info.num_pieces()
if self.has_metadata
else 0,
'piece_length': lambda: self.torrent_info.piece_length()
if self.has_metadata
else 0,
'private': lambda: self.torrent_info.priv() if self.has_metadata else False,
'total_size': lambda: self.torrent_info.total_size() if self.has_metadata else 0,
'total_size': lambda: self.torrent_info.total_size()
if self.has_metadata
else 0,
'eta': self.get_eta,
'file_progress': self.get_file_progress,
'files': self.get_files,
@ -1061,7 +1194,7 @@ class Torrent(object):
'super_seeding': lambda: self.status.super_seeding,
'time_since_download': lambda: self.status.time_since_download,
'time_since_upload': lambda: self.status.time_since_upload,
'time_since_transfer': self.get_time_since_transfer
'time_since_transfer': self.get_time_since_transfer,
}
def pause(self):
@ -1072,37 +1205,48 @@ class Torrent(object):
"""
# Turn off auto-management so the torrent will not be unpaused by lt queueing
self.handle.auto_managed(False)
self._set_handle_flags(
flag=lt.torrent_flags.auto_managed,
set_flag=False,
)
if self.state == 'Error':
return False
log.debug('Unable to pause torrent while in Error state')
elif self.status.paused:
# This torrent was probably paused due to being auto managed by lt
# Since we turned auto_managed off, we should update the state which should
# show it as 'Paused'. We need to emit a torrent_paused signal because
# the torrent_paused alert from libtorrent will not be generated.
self.update_state()
component.get('EventManager').emit(TorrentStateChangedEvent(self.torrent_id, 'Paused'))
component.get('EventManager').emit(
TorrentStateChangedEvent(self.torrent_id, 'Paused')
)
else:
try:
self.handle.pause()
except RuntimeError as ex:
log.debug('Unable to pause torrent: %s', ex)
return False
return True
def resume(self):
"""Resumes this torrent."""
if self.status.paused and self.status.auto_managed:
log.debug('Resume not possible for auto-managed torrent!')
elif self.forced_error and self.forced_error.was_paused:
log.debug('Resume skipped for forced_error torrent as it was originally paused.')
elif (self.status.is_finished and self.options['stop_at_ratio'] and
self.get_ratio() >= self.options['stop_ratio']):
log.debug(
'Resume skipped for forced_error torrent as it was originally paused.'
)
elif (
self.status.is_finished
and self.options['stop_at_ratio']
and self.get_ratio() >= self.options['stop_ratio']
):
log.debug('Resume skipped for torrent as it has reached "stop_seed_ratio".')
else:
# Check if torrent was originally being auto-managed.
if self.options['auto_managed']:
self.handle.auto_managed(True)
self._set_handle_flags(
flag=lt.torrent_flags.auto_managed,
set_flag=True,
)
try:
self.handle.resume()
except RuntimeError as ex:
@ -1125,8 +1269,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
@ -1147,9 +1291,13 @@ class Torrent(object):
try:
os.makedirs(dest)
except OSError as ex:
log.error('Could not move storage for torrent %s since %s does '
'not exist and could not create the directory: %s',
self.torrent_id, dest, ex)
log.error(
'Could not move storage for torrent %s since %s does '
'not exist and could not create the directory: %s',
self.torrent_id,
dest,
ex,
)
return False
try:
@ -1182,8 +1330,9 @@ class Torrent(object):
flags = lt.save_resume_flags_t.flush_disk_cache if flush_disk_cache else 0
# Don't generate fastresume data if torrent is in a Deluge Error state.
if self.forced_error:
component.get('TorrentManager').waiting_on_resume_data[self.torrent_id].errback(
UserWarning('Skipped creating resume_data while in Error state'))
component.get('TorrentManager').waiting_on_resume_data[
self.torrent_id
].errback(UserWarning('Skipped creating resume_data while in Error state'))
else:
self.handle.save_resume_data(flags)
@ -1201,16 +1350,15 @@ class Torrent(object):
try:
with open(filepath, 'wb') as save_file:
save_file.write(filedump)
except IOError as ex:
except OSError as ex:
log.error('Unable to save torrent file to: %s', ex)
filepath = os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')
# Regenerate the file priorities
self.set_file_priorities([])
if filedump is None:
metadata = lt.bdecode(self.torrent_info.metadata())
torrent_file = {b'info': metadata}
filedump = lt.bencode(torrent_file)
lt_ct = lt.create_torrent(self.torrent_info)
filedump = lt.bencode(lt_ct.generate())
write_file(filepath, filedump)
# If the user has requested a copy of the torrent be saved elsewhere we need to do that.
@ -1222,9 +1370,13 @@ class Torrent(object):
def delete_torrentfile(self, delete_copies=False):
"""Deletes the .torrent file in the state directory in config"""
torrent_files = [os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')]
if delete_copies:
torrent_files.append(os.path.join(self.config['torrentfiles_location'], self.filename))
torrent_files = [
os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')
]
if delete_copies and self.filename:
torrent_files.append(
os.path.join(self.config['torrentfiles_location'], self.filename)
)
for torrent_file in torrent_files:
log.debug('Deleting torrent file: %s', torrent_file)
@ -1245,8 +1397,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()
@ -1284,7 +1436,7 @@ class Torrent(object):
# lt needs utf8 byte-string. Otherwise if wstrings enabled, unicode string.
try:
self.handle.rename_file(index, filename.encode('utf8'))
except TypeError:
except (UnicodeDecodeError, TypeError):
self.handle.rename_file(index, filename)
def rename_folder(self, folder, new_folder):
@ -1293,7 +1445,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:
@ -1320,15 +1472,19 @@ class Torrent(object):
new_path = _file['path'].replace(folder, new_folder, 1)
try:
self.handle.rename_file(_file['index'], new_path.encode('utf8'))
except TypeError:
except (UnicodeDecodeError, TypeError):
self.handle.rename_file(_file['index'], new_path)
def on_folder_rename_complete(dummy_result, torrent, folder, new_folder):
"""Folder rename complete"""
component.get('EventManager').emit(TorrentFolderRenamedEvent(torrent.torrent_id, folder, new_folder))
component.get('EventManager').emit(
TorrentFolderRenamedEvent(torrent.torrent_id, folder, new_folder)
)
# Empty folders are removed after libtorrent folder renames
self.remove_empty_folders(folder)
torrent.waiting_on_folder_rename = [_dir for _dir in torrent.waiting_on_folder_rename if _dir]
torrent.waiting_on_folder_rename = [
_dir for _dir in torrent.waiting_on_folder_rename if _dir
]
component.get('TorrentManager').save_resume_data((self.torrent_id,))
d = DeferredList(list(wait_on_folder.values()))
@ -1345,7 +1501,9 @@ class Torrent(object):
"""
# Removes leading slashes that can cause join to ignore download_location
download_location = self.options['download_location']
folder_full_path = os.path.normpath(os.path.join(download_location, folder.lstrip('\\/')))
folder_full_path = os.path.normpath(
os.path.join(download_location, folder.lstrip('\\/'))
)
try:
if not os.listdir(folder_full_path):
@ -1356,7 +1514,9 @@ class Torrent(object):
for name in dirs:
try:
os.removedirs(os.path.join(root, name))
log.debug('Removed Empty Folder %s', os.path.join(root, name))
log.debug(
'Removed Empty Folder %s', os.path.join(root, name)
)
except OSError as ex:
log.debug(ex)
@ -1379,16 +1539,22 @@ class Torrent(object):
pieces = None
else:
pieces = []
for piece, avail_piece in zip(self.status.pieces, self.handle.piece_availability()):
for piece, avail_piece in zip(
self.status.pieces, self.handle.piece_availability()
):
if piece:
pieces.append(3) # Completed.
# Completed.
pieces.append(3)
elif avail_piece:
pieces.append(1) # Available, just not downloaded nor being downloaded.
# Available, just not downloaded nor being downloaded.
pieces.append(1)
else:
pieces.append(0) # Missing, no known peer with piece, or not asked for yet.
# Missing, no known peer with piece, or not asked for yet.
pieces.append(0)
for peer_info in self.handle.get_peer_info():
if peer_info.downloading_piece_index >= 0:
pieces[peer_info.downloading_piece_index] = 2 # Being downloaded from peer.
# Being downloaded from peer.
pieces[peer_info.downloading_piece_index] = 2
return pieces

File diff suppressed because it is too large Load diff

136
deluge/crypto_utils.py Normal file
View file

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

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
@ -7,12 +6,13 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import inspect
import re
import warnings
from functools import wraps
from typing import Any, Callable, Coroutine, TypeVar
from twisted.internet import defer
def proxy(proxy_func):
@ -23,11 +23,14 @@ def proxy(proxy_func):
:param proxy_func: the proxy function
:type proxy_func: function
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return proxy_func(func, *args, **kwargs)
return wrapper
return decorator
@ -53,10 +56,11 @@ 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)
return ret_func
@ -75,7 +79,10 @@ def _overrides(stack, method, explicit_base_classes=None):
check_classes = base_classes
if not base_classes:
raise ValueError('overrides decorator: unable to determine base class of class "%s"' % class_name)
raise ValueError(
'overrides decorator: unable to determine base class of class "%s"'
% class_name
)
def get_class(cls_name):
if '.' not in cls_name:
@ -91,47 +98,138 @@ def _overrides(stack, method, explicit_base_classes=None):
if explicit_base_classes:
# One or more base classes are explicitly given, check only those classes
override_classes = re.search(r'\s*@overrides\((.+)\)\s*', stack[1][4][0]).group(1)
override_classes = re.search(r'\s*@overrides\((.+)\)\s*', stack[1][4][0]).group(
1
)
override_classes = [c.strip() for c in override_classes.split(',')]
check_classes = override_classes
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
for bc, cc in product(base_classes, check_classes):
if issubclass(classes[bc], classes[cc]):
break
else:
raise Exception('Excplicit override class "%s" is not a super class of: %s'
% (explicit_base_classes, class_name))
raise Exception(
'Excplicit override class "%s" is not a super class of: %s'
% (explicit_base_classes, class_name)
)
if not all(hasattr(classes[cls], method.__name__) for cls in check_classes):
for cls in check_classes:
if not hasattr(classes[cls], method.__name__):
raise Exception('Function override "%s" not found in superclass: %s\n%s'
% (method.__name__, cls, 'File: %s:%s' % (stack[1][1], stack[1][2])))
raise Exception(
'Function override "%s" not found in superclass: %s\n%s'
% (
method.__name__,
cls,
f'File: {stack[1][1]}:{stack[1][2]}',
)
)
if not any(hasattr(classes[cls], method.__name__) for cls in check_classes):
raise Exception('Function override "%s" not found in any superclass: %s\n%s'
% (method.__name__, check_classes, 'File: %s:%s' % (stack[1][1], stack[1][2])))
raise Exception(
'Function override "%s" not found in any superclass: %s\n%s'
% (
method.__name__,
check_classes,
f'File: {stack[1][1]}:{stack[1][2]}',
)
)
return method
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.
"""
@wraps(func)
def depr_func(*args, **kwargs):
warnings.simplefilter('always', DeprecationWarning) # Turn off filter
warnings.warn('Call to deprecated function {}.'.format(func.__name__),
category=DeprecationWarning, stacklevel=2)
warnings.warn(
f'Call to deprecated function {func.__name__}.',
category=DeprecationWarning,
stacklevel=2,
)
warnings.simplefilter('default', DeprecationWarning) # Reset filter
return func(*args, **kwargs)
return depr_func
class CoroutineDeferred(defer.Deferred):
"""Wraps a coroutine in a Deferred.
It will dynamically pass through the underlying coroutine without wrapping where apporpriate.
"""
def __init__(self, coro: Coroutine):
# Delay this import to make sure a reactor was installed first
from twisted.internet import reactor
super().__init__()
self.coro = coro
self.awaited = None
self.activate_deferred = reactor.callLater(0, self.activate)
def __await__(self):
if self.awaited in [None, True]:
self.awaited = True
return self.coro.__await__()
# Already in deferred mode
return super().__await__()
def activate(self):
"""If the result wasn't awaited before the next context switch, we turn it into a deferred."""
if self.awaited is None:
self.awaited = False
try:
d = defer.Deferred.fromCoroutine(self.coro)
except AttributeError:
# Fallback for Twisted <= 21.2 without fromCoroutine
d = defer.ensureDeferred(self.coro)
d.chainDeferred(self)
def _callback_activate(self):
"""Verify awaited status before calling activate."""
assert not self.awaited, 'Cannot add callbacks to an already awaited coroutine.'
self.activate()
def addCallback(self, *args, **kwargs): # noqa: N802
self._callback_activate()
return super().addCallback(*args, **kwargs)
def addCallbacks(self, *args, **kwargs): # noqa: N802
self._callback_activate()
return super().addCallbacks(*args, **kwargs)
def addErrback(self, *args, **kwargs): # noqa: N802
self._callback_activate()
return super().addErrback(*args, **kwargs)
def addBoth(self, *args, **kwargs): # noqa: N802
self._callback_activate()
return super().addBoth(*args, **kwargs)
_RetT = TypeVar('_RetT')
def maybe_coroutine(
f: Callable[..., Coroutine[Any, Any, _RetT]],
) -> 'Callable[..., defer.Deferred[_RetT]]':
"""Wraps a coroutine function to make it usable as a normal function that returns a Deferred."""
@wraps(f)
def wrapper(*args, **kwargs):
# Uncomment for quick testing to make sure CoroutineDeferred magic isn't at fault
# return defer.ensureDeferred(f(*args, **kwargs))
return CoroutineDeferred(f(*args, **kwargs))
return wrapper

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@ -9,19 +8,15 @@
#
from __future__ import unicode_literals
class DelugeError(Exception):
def __new__(cls, *args, **kwargs):
inst = super(DelugeError, cls).__new__(cls, *args, **kwargs)
inst = super().__new__(cls, *args, **kwargs)
inst._args = args
inst._kwargs = kwargs
return inst
def __init__(self, message=None):
super(DelugeError, self).__init__(message)
super().__init__(message)
self.message = message
def __str__(self):
@ -45,14 +40,13 @@ class InvalidPathError(DelugeError):
class WrappedException(DelugeError):
def __init__(self, message, exception_type, traceback):
super(WrappedException, self).__init__(message)
super().__init__(message)
self.type = exception_type
self.traceback = traceback
def __str__(self):
return '%s\n%s' % (self.message, self.traceback)
return f'{self.message}\n{self.traceback}'
class _ClientSideRecreateError(DelugeError):
@ -60,29 +54,29 @@ class _ClientSideRecreateError(DelugeError):
class IncompatibleClient(_ClientSideRecreateError):
def __init__(self, daemon_version):
self.daemon_version = daemon_version
msg = 'Your deluge client is not compatible with the daemon. '\
'Please upgrade your client to %(daemon_version)s' % \
dict(daemon_version=self.daemon_version)
super(IncompatibleClient, self).__init__(message=msg)
msg = (
'Your deluge client is not compatible with the daemon. '
'Please upgrade your client to %(daemon_version)s'
) % {'daemon_version': self.daemon_version}
super().__init__(message=msg)
class NotAuthorizedError(_ClientSideRecreateError):
def __init__(self, current_level, required_level):
msg = 'Auth level too low: %(current_level)s < %(required_level)s' % \
dict(current_level=current_level, required_level=required_level)
super(NotAuthorizedError, self).__init__(message=msg)
msg = ('Auth level too low: %(current_level)s < %(required_level)s') % {
'current_level': current_level,
'required_level': required_level,
}
super().__init__(message=msg)
self.current_level = current_level
self.required_level = required_level
class _UsernameBasedPasstroughError(_ClientSideRecreateError):
def __init__(self, message, username):
super(_UsernameBasedPasstroughError, self).__init__(message)
super().__init__(message)
self.username = username
@ -96,3 +90,7 @@ class AuthenticationRequired(_UsernameBasedPasstroughError):
class AuthManagerError(_UsernameBasedPasstroughError):
pass
class LibtorrentImportError(ImportError):
pass

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@ -14,7 +13,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
known_events = {}
@ -23,13 +21,14 @@ class DelugeEventMetaClass(type):
"""
This metaclass simply keeps a list of all events classes created.
"""
def __init__(self, name, bases, dct): # pylint: disable=bad-mcs-method-argument
super(DelugeEventMetaClass, self).__init__(name, bases, dct)
def __init__(cls, name, bases, dct): # pylint: disable=bad-mcs-method-argument
super().__init__(name, bases, dct)
if name != 'DelugeEvent':
known_events[name] = self
known_events[name] = cls
class DelugeEvent(object):
class DelugeEvent(metaclass=DelugeEventMetaClass):
"""
The base class for all events.
@ -39,7 +38,6 @@ class DelugeEvent(object):
:type args: list
"""
__metaclass__ = DelugeEventMetaClass
def _get_name(self):
return self.__class__.__name__
@ -57,6 +55,7 @@ class TorrentAddedEvent(DelugeEvent):
"""
Emitted when a new torrent is successfully added to the session.
"""
def __init__(self, torrent_id, from_state):
"""
:param torrent_id: the torrent_id of the torrent that was added
@ -71,6 +70,7 @@ class TorrentRemovedEvent(DelugeEvent):
"""
Emitted when a torrent has been removed from the session.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -83,6 +83,7 @@ class PreTorrentRemovedEvent(DelugeEvent):
"""
Emitted when a torrent is about to be removed from the session.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -95,6 +96,7 @@ class TorrentStateChangedEvent(DelugeEvent):
"""
Emitted when a torrent changes state.
"""
def __init__(self, torrent_id, state):
"""
:param torrent_id: the torrent_id
@ -109,6 +111,7 @@ class TorrentTrackerStatusEvent(DelugeEvent):
"""
Emitted when a torrents tracker status changes.
"""
def __init__(self, torrent_id, status):
"""
Args:
@ -122,6 +125,7 @@ class TorrentQueueChangedEvent(DelugeEvent):
"""
Emitted when the queue order has changed.
"""
pass
@ -129,6 +133,7 @@ class TorrentFolderRenamedEvent(DelugeEvent):
"""
Emitted when a folder within a torrent has been renamed.
"""
def __init__(self, torrent_id, old, new):
"""
:param torrent_id: the torrent_id
@ -145,6 +150,7 @@ class TorrentFileRenamedEvent(DelugeEvent):
"""
Emitted when a file within a torrent has been renamed.
"""
def __init__(self, torrent_id, index, name):
"""
:param torrent_id: the torrent_id
@ -161,6 +167,7 @@ class TorrentFinishedEvent(DelugeEvent):
"""
Emitted when a torrent finishes downloading.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -173,6 +180,7 @@ class TorrentResumedEvent(DelugeEvent):
"""
Emitted when a torrent resumes from a paused state.
"""
def __init__(self, torrent_id):
"""
:param torrent_id: the torrent_id
@ -185,6 +193,7 @@ class TorrentFileCompletedEvent(DelugeEvent):
"""
Emitted when a file completes.
"""
def __init__(self, torrent_id, index):
"""
:param torrent_id: the torrent_id
@ -199,6 +208,7 @@ class TorrentStorageMovedEvent(DelugeEvent):
"""
Emitted when the storage location for a torrent has been moved.
"""
def __init__(self, torrent_id, path):
"""
:param torrent_id: the torrent_id
@ -213,6 +223,7 @@ class CreateTorrentProgressEvent(DelugeEvent):
"""
Emitted when creating a torrent file remotely.
"""
def __init__(self, piece_count, num_pieces):
self._args = [piece_count, num_pieces]
@ -221,6 +232,7 @@ class NewVersionAvailableEvent(DelugeEvent):
"""
Emitted when a more recent version of Deluge is available.
"""
def __init__(self, new_release):
"""
:param new_release: the new version that is available
@ -234,6 +246,7 @@ class SessionStartedEvent(DelugeEvent):
Emitted when a session has started. This typically only happens once when
the daemon is initially started.
"""
pass
@ -241,6 +254,7 @@ class SessionPausedEvent(DelugeEvent):
"""
Emitted when the session has been paused.
"""
pass
@ -248,6 +262,7 @@ class SessionResumedEvent(DelugeEvent):
"""
Emitted when the session has been resumed.
"""
pass
@ -255,6 +270,7 @@ class ConfigValueChangedEvent(DelugeEvent):
"""
Emitted when a config value changes in the Core.
"""
def __init__(self, key, value):
"""
:param key: the key that changed
@ -268,6 +284,7 @@ class PluginEnabledEvent(DelugeEvent):
"""
Emitted when a plugin is enabled in the Core.
"""
def __init__(self, plugin_name):
self._args = [plugin_name]
@ -276,6 +293,7 @@ class PluginDisabledEvent(DelugeEvent):
"""
Emitted when a plugin is disabled in the Core.
"""
def __init__(self, plugin_name):
self._args = [plugin_name]
@ -284,6 +302,7 @@ class ClientDisconnectedEvent(DelugeEvent):
"""
Emitted when a client disconnects.
"""
def __init__(self, session_id):
self._args = [session_id]
@ -292,6 +311,7 @@ class ExternalIPEvent(DelugeEvent):
"""
Emitted when the external ip address is received from libtorrent.
"""
def __init__(self, external_ip):
"""
Args:

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@ -7,129 +6,203 @@
# See LICENSE for more details.
#
from __future__ import unicode_literals
import email.message
import logging
import os.path
import zlib
from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.python.failure import Failure
from twisted.web import client, http
from twisted.web.error import PageRedirect
from twisted.web._newclient import HTTPClientParser
from twisted.web.error import Error, PageRedirect
from twisted.web.http_headers import Headers
from twisted.web.iweb import IAgent
from zope.interface import implementer
from deluge.common import get_version, utf8_encode_structure
try:
from urllib.parse import urljoin
except ImportError:
# PY2 fallback
from urlparse import urljoin # pylint: disable=ungrouped-imports
from deluge.common import get_version
log = logging.getLogger(__name__)
class HTTPDownloader(client.HTTPDownloader):
"""
Factory class for downloading files and keeping track of progress.
"""
def __init__(self, url, filename, part_callback=None, headers=None,
force_filename=False, allow_compression=True):
class CompressionDecoder(client.GzipDecoder):
"""A compression decoder for gzip, x-gzip and deflate."""
def deliverBody(self, protocol): # NOQA: N802
self.original.deliverBody(CompressionDecoderProtocol(protocol, self.original))
class CompressionDecoderProtocol(client._GzipProtocol):
"""A compression decoder protocol for CompressionDecoder."""
def __init__(self, protocol, response):
super().__init__(protocol, response)
self._zlibDecompress = zlib.decompressobj(32 + zlib.MAX_WBITS)
class BodyHandler(HTTPClientParser):
"""An HTTP parser that saves the response to a file."""
def __init__(self, request, finished, length, agent, encoding=None):
"""BodyHandler init.
Args:
request (t.w.i.IClientRequest): The parser request.
finished (Deferred): A Deferred to handle the finished response.
length (int): The length of the response.
agent (t.w.i.IAgent): The agent from which the request was sent.
"""
:param url: the url to download from
:type url: string
:param filename: the filename to save the file as
:type filename: string
:param force_filename: forces use of the supplied filename, regardless of header content
:type force_filename: bool
:param part_callback: a function to be called when a part of data
is received, it's signature should be: func(data, current_length, total_length)
:type part_callback: function
:param headers: any optional headers to send
:type headers: dictionary
super().__init__(request, finished)
self.agent = agent
self.finished = finished
self.total_length = length
self.current_length = 0
self.data = b''
self.encoding = encoding
def dataReceived(self, data): # NOQA: N802
self.current_length += len(data)
self.data += data
if self.agent.part_callback:
self.agent.part_callback(data, self.current_length, self.total_length)
def connectionLost(self, reason): # NOQA: N802
if self.encoding:
self.data = self.data.decode(self.encoding).encode('utf8')
with open(self.agent.filename, 'wb') as _file:
_file.write(self.data)
self.finished.callback(self.agent.filename)
self.state = 'DONE'
HTTPClientParser.connectionLost(self, reason)
@implementer(IAgent)
class HTTPDownloaderAgent:
"""A File Downloader Agent."""
def __init__(
self,
agent,
filename,
part_callback=None,
force_filename=False,
allow_compression=True,
handle_redirect=True,
):
"""HTTPDownloaderAgent init.
Args:
agent (t.w.c.Agent): The agent which will send the requests.
filename (str): The filename to save the file as.
force_filename (bool): Forces use of the supplied filename,
regardless of header content.
part_callback (func): A function to be called when a part of data
is received, it's signature should be:
func(data, current_length, total_length)
"""
self.handle_redirect = handle_redirect
self.agent = agent
self.filename = filename
self.part_callback = part_callback
self.current_length = 0
self.total_length = 0
self.decoder = None
self.value = filename
self.force_filename = force_filename
self.allow_compression = allow_compression
self.code = None
agent = b'Deluge/%s (http://deluge-torrent.org)' % get_version().encode('utf8')
self.decoder = None
client.HTTPDownloader.__init__(self, url, filename, headers=headers, agent=agent)
def request_callback(self, response):
finished = Deferred()
def gotStatus(self, version, status, message): # NOQA: N802
self.code = int(status)
client.HTTPDownloader.gotStatus(self, version, status, message)
if not self.handle_redirect and response.code in (
http.MOVED_PERMANENTLY,
http.FOUND,
http.SEE_OTHER,
http.TEMPORARY_REDIRECT,
):
location = response.headers.getRawHeaders(b'location')[0]
error = PageRedirect(response.code, location=location)
finished.errback(Failure(error))
elif response.code >= 400:
error = Error(response.code)
finished.errback(Failure(error))
else:
headers = response.headers
body_length = int(headers.getRawHeaders(b'content-length', default=[0])[0])
def gotHeaders(self, headers): # NOQA: N802
if self.code == http.OK:
if 'content-length' in headers:
self.total_length = int(headers['content-length'][0])
else:
self.total_length = 0
if headers.hasHeader(b'content-disposition') and not self.force_filename:
content_disp = headers.getRawHeaders(b'content-disposition')[0].decode(
'utf-8'
)
message = email.message.EmailMessage()
message['content-disposition'] = content_disp
new_file_name = message.get_filename()
if new_file_name:
new_file_name = sanitise_filename(new_file_name)
new_file_name = os.path.join(
os.path.split(self.filename)[0], new_file_name
)
if self.allow_compression and 'content-encoding' in headers and \
headers['content-encoding'][0] in ('gzip', 'x-gzip', 'deflate'):
# Adding 32 to the wbits enables gzip & zlib decoding (with automatic header detection)
# Adding 16 just enables gzip decoding (no zlib)
self.decoder = zlib.decompressobj(zlib.MAX_WBITS + 32)
count = 1
fileroot = os.path.splitext(new_file_name)[0]
fileext = os.path.splitext(new_file_name)[1]
while os.path.isfile(new_file_name):
# Increment filename if already exists
new_file_name = f'{fileroot}-{count}{fileext}'
count += 1
if 'content-disposition' in headers and not self.force_filename:
new_file_name = str(headers['content-disposition'][0]).split(';')[1].split('=')[1]
new_file_name = sanitise_filename(new_file_name)
new_file_name = os.path.join(os.path.split(self.value)[0], new_file_name)
self.filename = new_file_name
count = 1
fileroot = os.path.splitext(new_file_name)[0]
fileext = os.path.splitext(new_file_name)[1]
while os.path.isfile(new_file_name):
# Increment filename if already exists
new_file_name = '%s-%s%s' % (fileroot, count, fileext)
count += 1
cont_type_header = headers.getRawHeaders(b'content-type')[0].decode()
message = email.message.EmailMessage()
message['content-type'] = cont_type_header
cont_type = message.get_content_type()
params = message['content-type'].params
# 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)
)
self.fileName = new_file_name
self.value = new_file_name
return finished
elif self.code in (http.MOVED_PERMANENTLY, http.FOUND, http.SEE_OTHER, http.TEMPORARY_REDIRECT):
location = headers['location'][0]
error = PageRedirect(self.code, location=location)
self.noPage(Failure(error))
def request(self, method, uri, headers=None, body_producer=None):
"""Issue a new request to the wrapped agent.
return client.HTTPDownloader.gotHeaders(self, headers)
Args:
method (bytes): The HTTP method to use.
uri (bytes): The url to download from.
headers (t.w.h.Headers, optional): Any extra headers to send.
body_producer (t.w.i.IBodyProducer, optional): Request body data.
def pagePart(self, data): # NOQA: N802
if self.code == http.OK:
self.current_length += len(data)
if self.decoder:
data = self.decoder.decompress(data)
if self.part_callback:
self.part_callback(data, self.current_length, self.total_length)
Returns:
Deferred: The filename of the of the downloaded file.
"""
if headers is None:
headers = Headers()
return client.HTTPDownloader.pagePart(self, data)
if not headers.hasHeader(b'User-Agent'):
version = get_version()
user_agent = 'Deluge/%s (https://deluge-torrent.org)' % version
headers.addRawHeader('User-Agent', user_agent)
def pageEnd(self): # NOQA: N802
if self.decoder:
data = self.decoder.flush()
self.current_length -= len(data)
self.decoder = None
self.pagePart(data)
return client.HTTPDownloader.pageEnd(self)
d = self.agent.request(
method=method, uri=uri, headers=headers, bodyProducer=body_producer
)
d.addCallback(self.request_callback)
return d
def sanitise_filename(filename):
"""
Sanitises a filename to use as a download destination file.
"""Sanitises a filename to use as a download destination file.
Logs any filenames that could be considered malicious.
:param filename: the filename to sanitise
:type filename: string
:returns: the sanitised filename
:rtype: string
filename (str): The filename to sanitise.
Returns:
str: The sanitised filename.
"""
# Remove any quotes
@ -137,136 +210,128 @@ def sanitise_filename(filename):
if os.path.basename(filename) != filename:
# Dodgy server, log it
log.warning('Potentially malicious server: trying to write to file: %s', filename)
log.warning(
'Potentially malicious server: trying to write to file: %s', filename
)
# Only use the basename
filename = os.path.basename(filename)
filename = filename.strip()
if filename.startswith('.') or ';' in filename or '|' in filename:
# Dodgy server, log it
log.warning('Potentially malicious server: trying to write to file: %s', filename)
log.warning(
'Potentially malicious server: trying to write to file: %s', filename
)
return filename
def _download_file(url, filename, callback=None, headers=None, force_filename=False, allow_compression=True):
"""
Downloads a file from a specific URL and returns a Deferred. A callback
function can be specified to be called as parts are received.
def _download_file(
url,
filename,
callback=None,
headers=None,
force_filename=False,
allow_compression=True,
handle_redirects=True,
):
"""Downloads a file from a specific URL and returns a Deferred.
A callback function can be specified to be called as parts are received.
Args:
url (str): The url to download from
filename (str): The filename to save the file as
callback (func): A function to be called when a part of data is received,
url (str): The url to download from.
filename (str): The filename to save the file as.
callback (func): A function to be called when partial data is received,
it's signature should be: func(data, current_length, total_length)
headers (dict): Any optional headers to send
force_filename (bool): force us to use the filename specified rather than
one the server may suggest
allow_compression (bool): Allows gzip & deflate decoding
headers (dict): Any optional headers to send.
force_filename (bool): Force using the filename specified rather than
one the server may suggest.
allow_compression (bool): Allows gzip & deflate decoding.
Returns:
Deferred: the filename of the downloaded file
Deferred: The filename of the downloaded file.
Raises:
t.w.e.PageRedirect
t.w.e.Error: for all other HTTP response errors
"""
agent = client.Agent(reactor)
if allow_compression:
if not headers:
headers = {}
headers['accept-encoding'] = 'deflate, gzip, x-gzip'
enc_accepted = ['gzip', 'x-gzip', 'deflate']
decoders = [(enc.encode(), CompressionDecoder) for enc in enc_accepted]
agent = client.ContentDecoderAgent(agent, decoders)
if handle_redirects:
agent = client.RedirectAgent(agent)
url = url.encode('utf8')
filename = filename.encode('utf8')
headers = utf8_encode_structure(headers) if headers else headers
factory = HTTPDownloader(url, filename, callback, headers, force_filename, allow_compression)
agent = HTTPDownloaderAgent(
agent, filename, callback, force_filename, allow_compression, handle_redirects
)
# In Twisted 13.1.0 _parse() function replaced by _URI class.
# In Twisted 15.0.0 _URI class renamed to URI.
if hasattr(client, '_parse'):
scheme, host, port, dummy_path = client._parse(url)
else:
try:
from twisted.web.client import _URI as URI
except ImportError:
from twisted.web.client import URI
finally:
uri = URI.fromBytes(url)
scheme = uri.scheme
host = uri.host
port = uri.port
# The Headers init expects dict values to be a list.
if headers:
for name, value in list(headers.items()):
if not isinstance(value, list):
headers[name] = [value]
if scheme == 'https':
from twisted.internet import ssl
# ClientTLSOptions in Twisted >= 14, see ticket #2765 for details on this addition.
try:
from twisted.internet._sslverify import ClientTLSOptions
except ImportError:
ctx_factory = ssl.ClientContextFactory()
else:
class TLSSNIContextFactory(ssl.ClientContextFactory): # pylint: disable=no-init
"""
A custom context factory to add a server name for TLS connections.
"""
def getContext(self): # NOQA: N802
ctx = ssl.ClientContextFactory.getContext(self)
ClientTLSOptions(host, ctx)
return ctx
ctx_factory = TLSSNIContextFactory()
reactor.connectSSL(host, port, factory, ctx_factory)
else:
reactor.connectTCP(host, port, factory)
return factory.deferred
return agent.request(b'GET', url.encode(), Headers(headers))
def download_file(url, filename, callback=None, headers=None, force_filename=False,
allow_compression=True, handle_redirects=True):
"""
Downloads a file from a specific URL and returns a Deferred. A callback
function can be specified to be called as parts are received.
def download_file(
url,
filename,
callback=None,
headers=None,
force_filename=False,
allow_compression=True,
handle_redirects=True,
):
"""Downloads a file from a specific URL and returns a Deferred.
A callback function can be specified to be called as parts are received.
Args:
url (str): The url to download from
filename (str): The filename to save the file as
callback (func): A function to be called when a part of data is received,
it's signature should be: func(data, current_length, total_length)
headers (dict): Any optional headers to send
force_filename (bool): force us to use the filename specified rather than
one the server may suggest
allow_compression (bool): Allows gzip & deflate decoding
handle_redirects (bool): If HTTP redirects should be handled automatically
url (str): The url to download from.
filename (str): The filename to save the file as.
callback (func): A function to be called when partial data is received,
it's signature should be: func(data, current_length, total_length).
headers (dict): Any optional headers to send.
force_filename (bool): Force the filename specified rather than one the
server may suggest.
allow_compression (bool): Allows gzip & deflate decoding.
handle_redirects (bool): HTTP redirects handled automatically or not.
Returns:
Deferred: the filename of the downloaded file
Deferred: The filename of the downloaded file.
Raises:
t.w.e.PageRedirect: Unless handle_redirects=True
t.w.e.Error: for all other HTTP response errors
t.w.e.PageRedirect: If handle_redirects is False.
t.w.e.Error: For all other HTTP response errors.
"""
def on_download_success(result):
log.debug('Download success!')
return result
def on_download_fail(failure):
if failure.check(PageRedirect) and handle_redirects:
new_url = urljoin(url, failure.getErrorMessage().split(' to ')[1])
result = _download_file(new_url, filename, callback=callback, headers=headers,
force_filename=force_filename,
allow_compression=allow_compression)
result.addCallbacks(on_download_success, on_download_fail)
else:
# Log the failure and pass to the caller
log.warning('Error occurred downloading file from "%s": %s',
url, failure.getErrorMessage())
result = failure
log.warning(
'Error occurred downloading file from "%s": %s',
url,
failure.getErrorMessage(),
)
result = failure
return result
d = _download_file(url, filename, callback=callback, headers=headers,
force_filename=force_filename, allow_compression=allow_compression)
d = _download_file(
url,
filename,
callback=callback,
headers=headers,
force_filename=force_filename,
allow_compression=allow_compression,
handle_redirects=handle_redirects,
)
d.addCallbacks(on_download_success, on_download_fail)
return d

15
deluge/i18n/__init__.py Normal file
View file

@ -0,0 +1,15 @@
from .util import (
I18N_DOMAIN,
get_languages,
set_language,
setup_mock_translation,
setup_translation,
)
__all__ = [
'I18N_DOMAIN',
'set_language',
'get_languages',
'setup_translation',
'setup_mock_translation',
]

6234
deluge/i18n/af.po Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6212
deluge/i18n/deluge.pot Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6217
deluge/i18n/fo.po Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6214
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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6222
deluge/i18n/km.po Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6214
deluge/i18n/ky.po Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,17 @@
# -*- coding: utf-8 -*-
#
# 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'
# Deferred translation
def _(message):
return message
# Languages we provide translations for, out of the box.
LANGUAGES = {
'af': _('Afrikaans'),
@ -107,3 +110,5 @@ LANGUAGES = {
'zh-hant': _('Traditional Chinese'),
'zh_TW': _('Chinese (Taiwan)'),
}
del _

6214
deluge/i18n/lb.po Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6214
deluge/i18n/ml.po Normal file

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