Compare commits

...

131 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
578 changed files with 351433 additions and 337298 deletions

View file

@ -19,37 +19,37 @@ on:
jobs:
windows_package:
runs-on: windows-2019
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.6, 1.2.15]
libtorrent: [2.0.7, 1.2.19]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v3
- 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@v3
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref }}
fetch-depth: 0
path: deluge_src
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python}}
architecture: ${{ matrix.arch }}
cache: pip
- name: Prepare pip
run: python -m pip install wheel
run: python -m pip install wheel setuptools==68.*
- name: Install GTK
run: |
@ -62,11 +62,14 @@ jobs:
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
twisted[tls]==22.4.0
--only-binary=pillow
twisted[tls]==22.8.0
libtorrent==${{ matrix.libtorrent }}
pyinstaller==4.10
pyinstaller
pygame
-r requirements.txt
@ -81,12 +84,13 @@ jobs:
run: |
pyinstaller --clean delugewin.spec --distpath freeze
- name: Fix OpenSSL for libtorrent x64
if: ${{ matrix.arch == 'x64' }}
working-directory: packaging/win/freeze/Deluge
- name: Verify Deluge exes
working-directory: packaging/win/freeze/Deluge/
run: |
cp libssl-1_1.dll libssl-1_1-x64.dll
cp libcrypto-1_1.dll libcrypto-1_1-x64.dll
deluge-debug.exe -v
deluged-debug.exe -v
deluge-web-debug.exe -v
deluge-console -v
- name: Make Deluge Installer
working-directory: ./packaging/win
@ -94,7 +98,7 @@ jobs:
python setup_nsis.py
makensis /Darch=${{ matrix.arch }} deluge-win-installer.nsi
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
with:
name: deluge-py${{ matrix.python }}-lt${{ matrix.libtorrent }}-${{ matrix.arch }}
path: packaging/win/*.exe

View file

@ -6,22 +6,25 @@ on:
# 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-20.04
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@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
@ -33,8 +36,8 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade pip wheel
pip install -r requirements.txt -r requirements-tests.txt
pip install --upgrade pip wheel setuptools
pip install -r requirements-ci.txt
pip install -e .
- name: Install security dependencies
@ -46,18 +49,21 @@ jobs:
TESTSSL_VER: 3.0.6
TESTSSL_URL: https://codeload.github.com/drwetter/testssl.sh/tar.gz/refs/tags/v
- name: Setup core dump directory
- 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: |
ulimit -c unlimited # Enable core dumps to be captured
python -c 'from deluge._libtorrent import lt; print(lt.__version__)';
catchsegv python -X dev -m pytest -v -m "not (todo or gtkui)" deluge
$DEBUG_PREFIX pytest -v -m "not (todo or gtkui)" deluge
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
# capture all crashes as build artifacts
if: failure()
with:
@ -65,19 +71,19 @@ jobs:
path: /cores
test-windows:
runs-on: windows-2019
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@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
@ -85,8 +91,8 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade pip wheel
pip install -r requirements.txt -r requirements-tests.txt
pip install --upgrade pip wheel setuptools
pip install -r requirements-ci.txt
pip install -e .
- name: Test with pytest

View file

@ -15,30 +15,23 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v2
- uses: actions/setup-python@v5
with:
python-version: "3.8"
- name: Cache pip
uses: actions/cache@v2
with:
# This path is specific to Ubuntu
path: ~/.cache/pip
# Look to see if there is a cache hit for the corresponding requirements file
key: ${{ runner.os }}-pip-${{ hashFiles('requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
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
sudo apt-get install enchant-2
- name: Test with tox
- name: Build docs with tox
env:
TOX_ENV: docs
run: |

View file

@ -11,7 +11,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Run pre-commit linting
uses: pre-commit/action@v2.0.2
uses: pre-commit/action@v3.0.1

4
.gitignore vendored
View file

@ -12,14 +12,14 @@ __pycache__/
*.tar.*
.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/

View file

@ -6,35 +6,25 @@ exclude: >
deluge/tests/data/.*svg|
)$
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.6.4
hooks:
- id: black
name: Fmt Black
- id: ruff
name: Chk Ruff
args: [--fix]
- id: ruff-format
name: Fmt Ruff
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.5.1
rev: v2.7.1
hooks:
- id: prettier
name: Fmt Prettier
# Workaround to list modified files only.
args: [--list-different]
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
name: Fmt isort
- repo: https://github.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
name: Chk Flake8
additional_dependencies:
- pep8-naming==0.12.1
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
rev: v4.4.0
hooks:
- id: double-quote-string-fixer
name: Fix Double-quotes
- id: end-of-file-fixer
name: Fix End-of-files
exclude_types: [javascript, css]
@ -44,8 +34,8 @@ repos:
- id: trailing-whitespace
name: Fix Trailing whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
rev: v3.3.1
hooks:
- id: pyupgrade
args: [--py36-plus]
args: [--py37-plus]
stages: [manual]

View file

@ -5,6 +5,14 @@
# 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
@ -14,9 +22,8 @@ formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7
install:
- requirements: requirements.txt
- requirements: docs/requirements.txt
- method: setuptools
- method: pip
path: .

View file

@ -1,6 +1,16 @@
# Changelog
## unreleased
## 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

View file

@ -50,7 +50,7 @@ All modules will require the [common](#common) section dependencies.
- [PyGObject]
- [Pycairo]
- [librsvg] _>= 2_
- [libappindicator3] w/GIR - Optional: Ubuntu system tray icon.
- [ayatanaappindicator3] w/GIR - Optional: Ubuntu system tray icon.
### MacOS
@ -95,6 +95,6 @@ All modules will require the [common](#common) section dependencies.
[mako]: https://www.makotemplates.org/
[pygame]: https://www.pygame.org/
[libnotify]: https://developer.gnome.org/libnotify/
[python-appindicator]: https://packages.ubuntu.com/xenial/python-appindicator
[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/

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

@ -14,6 +14,7 @@ Example:
>>> from deluge._libtorrent import lt
"""
from deluge.common import VersionSplit, get_version
from deluge.error import LibtorrentImportError

View file

@ -85,7 +85,6 @@ def bdecode(x):
class Bencached:
__slots__ = ['bencoded']
def __init__(self, s):

View file

@ -7,6 +7,7 @@
#
"""Common functions for various parts of Deluge to use."""
import base64
import binascii
import functools
@ -23,15 +24,21 @@ import tarfile
import time
from contextlib import closing
from datetime import datetime
from importlib import resources
from io import BytesIO
from pathlib import Path
from urllib.parse import unquote_plus, urljoin
from urllib.request import pathname2url
import pkg_resources
from deluge.decorators import deprecated
from deluge.error import InvalidPathError
try:
from importlib.metadata import distribution
except ImportError:
from pkg_resources import get_distribution as distribution
try:
import chardet
except ImportError:
@ -90,7 +97,7 @@ def get_version():
Returns:
str: The version of Deluge.
"""
return pkg_resources.get_distribution('Deluge').version
return distribution('Deluge').version
def get_default_config_dir(filename=None):
@ -290,20 +297,22 @@ def get_pixmap(fname):
return resource_filename('deluge', os.path.join('ui', 'data', 'pixmaps', fname))
def resource_filename(module, path):
"""Get filesystem path for a resource.
def resource_filename(module: str, path: str) -> str:
"""Get filesystem path for a non-python resource.
This function contains a work-around for pkg_resources.resource_filename
not returning the correct path with multiple packages installed.
So if there's a second deluge package, installed globally and another in
develop mode somewhere else, while pkg_resources.get_distribution('Deluge')
returns the proper deluge instance, pkg_resources.resource_filename
does not, it returns the first found on the python path, which is wrong.
Abstracts getting module resource files. Originally created to
workaround pkg_resources.resource_filename limitations with
multiple Deluge packages installed.
"""
return pkg_resources.get_distribution('Deluge').get_resource_filename(
pkg_resources._manager, os.path.join(*(module.split('.') + [path]))
)
path = Path(path)
try:
with resources.as_file(resources.files(module) / path) as resource_file:
return str(resource_file)
except AttributeError:
# Python <= 3.8
with resources.path(module, path.parts[0]) as resource_file:
return str(resource_file.joinpath(*path.parts[1:]))
def open_file(path, timestamp=None):
@ -415,25 +424,31 @@ def translate_size_units():
def fsize(fsize_b, precision=1, shortform=False):
"""Formats the bytes value into a string with KiB, MiB or GiB units.
"""Formats the bytes value into a string with KiB, MiB, GiB or TiB units.
Args:
fsize_b (int): The filesize in bytes.
precision (int): The filesize float precision.
precision (int): The output float precision, 1 by default.
shortform (bool): The output short|long form, False (long form) by default.
Returns:
str: A formatted string in KiB, MiB or GiB units.
str: A formatted string in KiB, MiB, GiB or TiB units.
Examples:
>>> fsize(112245)
'109.6 KiB'
>>> fsize(112245, precision=0)
'110 KiB'
>>> fsize(112245, shortform=True)
'109.6 K'
Note:
This function has been refactored for performance with the
fsize units being translated outside the function.
Notice that short forms K|M|G|T are synonymous here with
KiB|MiB|GiB|TiB. They are powers of 1024, not 1000.
"""
if fsize_b >= 1024**4:
@ -469,7 +484,7 @@ def fpcnt(dec, precision=2):
Args:
dec (float): The ratio in the range [0.0, 1.0].
precision (int): The percentage float precision.
precision (int): The output float precision, 2 by default.
Returns:
str: A formatted string representing a percentage.
@ -493,6 +508,8 @@ def fspeed(bps, precision=1, shortform=False):
Args:
bps (int): The speed in bytes per second.
precision (int): The output float precision, 1 by default.
shortform (bool): The output short|long form, False (long form) by default.
Returns:
str: A formatted string representing transfer speed.
@ -501,6 +518,10 @@ def fspeed(bps, precision=1, shortform=False):
>>> fspeed(43134)
'42.1 KiB/s'
Note:
Notice that short forms K|M|G|T are synonymous here with
KiB|MiB|GiB|TiB. They are powers of 1024, not 1000.
"""
if bps < 1024**2:
@ -537,7 +558,7 @@ def fpeer(num_peers, total_peers):
total_peers (int): The total number of peers.
Returns:
str: A formatted string 'num_peers (total_peers)' or total_peers < 0, just 'num_peers'.
str: A formatted string 'num_peers (total_peers)' or if total_peers < 0, just 'num_peers'.
Examples:
>>> fpeer(10, 20)
@ -586,16 +607,16 @@ def ftime(secs):
time_str = f'{secs // 604800}w {secs // 86400 % 7}d'
else:
time_str = f'{secs // 31449600}y {secs // 604800 % 52}w'
return time_str
def fdate(seconds, date_only=False, precision_secs=False):
"""Formats a date time string in the locale's date representation based on the systems timezone.
"""Formats a date time string in the locale's date representation based on the system's timezone.
Args:
seconds (float): Time in seconds since the Epoch.
precision_secs (bool): Include seconds in time format.
date_only (bool): Whether to include only the date, False by default.
precision_secs (bool): Include seconds in time format, False by default.
Returns:
str: A string in the locale's datetime representation or "" if seconds < 0
@ -620,10 +641,14 @@ def tokenize(text):
Returns:
list: A list of strings and/or numbers.
This function is used to implement robust tokenization of user input
It automatically coerces integer and floating point numbers, ignores
whitespace and knows how to separate numbers from strings even without
whitespace.
Note:
This function is used to implement robust tokenization of user input
It automatically coerces integer and floating point numbers, ignores
whitespace and knows how to separate numbers from strings even without
whitespace.
Possible optimization: move the 2 regexes outside of function.
"""
tokenized_input = []
for token in re.split(r'(\d+(?:\.\d+)?)', text):
@ -644,12 +669,16 @@ size_units = [
{'prefix': 'GiB', 'divider': 1024**3},
{'prefix': 'TiB', 'divider': 1024**4},
{'prefix': 'PiB', 'divider': 1024**5},
{'prefix': 'k', 'divider': 1000**1},
{'prefix': 'm', 'divider': 1000**2},
{'prefix': 'g', 'divider': 1000**3},
{'prefix': 't', 'divider': 1000**4},
{'prefix': 'p', 'divider': 1000**5},
{'prefix': 'KB', 'divider': 1000**1},
{'prefix': 'MB', 'divider': 1000**2},
{'prefix': 'GB', 'divider': 1000**3},
{'prefix': 'TB', 'divider': 1000**4},
{'prefix': 'PB', 'divider': 1000**5},
{'prefix': 'm', 'divider': 1000**2},
]
@ -692,6 +721,16 @@ def parse_human_size(size):
raise InvalidSize(msg % (size, tokens))
def anchorify_urls(text: str) -> str:
"""
Wrap all occurrences of text URLs with HTML
"""
url_pattern = r'((htt)|(ft)|(ud))ps?://\S+'
html_href_pattern = r'<a href="\g<0>">\g<0></a>'
return re.sub(url_pattern, html_href_pattern, text)
def is_url(url):
"""
A simple test to check if the URL is valid
@ -833,7 +872,7 @@ def create_magnet_uri(infohash, name=None, trackers=None):
Args:
infohash (str): The info-hash of the torrent.
name (str, optional): The name of the torrent.
trackers (list or dict, optional): A list of trackers or dict or {tracker: tier} pairs.
trackers (list or dict, optional): A list of trackers or a dict or some {tracker: tier} pairs.
Returns:
str: A magnet URI string.
@ -875,7 +914,7 @@ def get_path_size(path):
return os.path.getsize(path)
dir_size = 0
for (p, dummy_dirs, files) in os.walk(path):
for p, dummy_dirs, files in os.walk(path):
for _file in files:
filename = os.path.join(p, _file)
dir_size += os.path.getsize(filename)

View file

@ -59,11 +59,16 @@ class Component:
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().
@ -80,10 +85,10 @@ class Component:
**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.
"""
@ -111,9 +116,8 @@ class Component:
_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):
@ -129,13 +133,10 @@ class Component:
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':
@ -165,14 +166,11 @@ class Component:
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
@ -182,13 +180,12 @@ class Component:
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:
@ -205,9 +202,10 @@ class Component:
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(
@ -222,9 +220,7 @@ class Component:
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)
@ -245,6 +241,12 @@ class Component:
def shutdown(self):
pass
def pause(self):
pass
def resume(self):
pass
class ComponentRegistry:
"""The ComponentRegistry holds a list of currently registered :class:`Component` objects.

View file

@ -38,6 +38,7 @@ this can only be done for the 'config file version' and not for the 'format'
version as this will be done internally.
"""
import json
import logging
import os

View file

@ -3,7 +3,7 @@
# 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
@ -12,7 +12,7 @@ import pytest
import pytest_twisted
from twisted.internet import reactor
from twisted.internet.defer import Deferred, maybeDeferred
from twisted.internet.error import CannotListenError
from twisted.internet.error import CannotListenError, ProcessTerminated
from twisted.python.failure import Failure
import deluge.component as _component
@ -42,15 +42,18 @@ def mock_callback():
The returned Mock instance will have a `deferred` attribute which will complete when the callback has been called.
"""
def reset():
def reset(timeout=0.5, *args, **kwargs):
if mock.called:
original_reset_mock()
deferred = Deferred()
deferred.addTimeout(0.5, reactor)
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()
@ -59,8 +62,9 @@ def mock_callback():
@pytest.fixture
def config_dir(tmp_path):
deluge.configmanager.set_config_dir(tmp_path)
yield tmp_path
config_dir = tmp_path / 'config'
deluge.configmanager.set_config_dir(config_dir)
yield config_dir
@pytest_twisted.async_yield_fixture()
@ -84,9 +88,10 @@ async def client(request, config_dir, monkeypatch, listen_port):
@pytest_twisted.async_yield_fixture
async def daemon(request, config_dir):
async def daemon(request, config_dir, tmp_path):
listen_port = DEFAULT_LISTEN_PORT
logfile = f'daemon_{request.node.name}.log'
logfile = tmp_path / 'daemon.log'
if hasattr(request.cls, 'daemon_custom_script'):
custom_script = request.cls.daemon_custom_script
else:
@ -116,7 +121,10 @@ async def daemon(request, config_dir):
raise exception_error
daemon.listen_port = listen_port
yield daemon
await daemon.kill()
try:
await daemon.kill()
except ProcessTerminated:
pass
@pytest.fixture(autouse=True)
@ -137,7 +145,7 @@ def common_fixture(config_dir, request, monkeypatch, listen_port):
@pytest_twisted.async_yield_fixture(scope='function')
async def component(request):
async def component():
"""Verify component registry is clean, and clean up after test."""
if len(_component._ComponentRegistry.components) != 0:
warnings.warn(
@ -190,3 +198,18 @@ def mock_mkstemp(tmp_path):
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

@ -14,10 +14,16 @@ This should typically only be used by the Core. Plugins should utilize the
`:mod:EventManager` for similar functionality.
"""
import logging
from types import SimpleNamespace
from twisted.internet import reactor
import 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, task, threads
import deluge.component as component
from deluge._libtorrent import lt
@ -31,7 +37,7 @@ class AlertManager(component.Component):
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.
@ -52,48 +58,88 @@ class AlertManager(component.Component):
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):
"""
@ -112,26 +158,32 @@ class AlertManager(component.Component):
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)
# Copy alert attributes
alert_copy = SimpleNamespace(
**{
attr: getattr(alert, attr)
for attr in dir(alert)
if not attr.startswith('__')
}
)
self.delayed_calls.append(reactor.callLater(0, handler, alert_copy))
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"""

View file

@ -12,17 +12,16 @@ import logging
import os
import shutil
import tempfile
import threading
from base64 import b64decode, b64encode
from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.request import URLError, urlopen
from twisted.internet import defer, reactor, task
from twisted.internet import defer, reactor, task, threads
from twisted.web.client import Agent, readBody
import deluge.common
import deluge.component as component
from deluge import path_chooser_common
from deluge import metafile, path_chooser_common
from deluge._libtorrent import LT_VERSION, lt
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.alertmanager import AlertManager
@ -199,7 +198,7 @@ class Core(component.Component):
self.session_status_timer_interval = 0.5
self.session_status_timer = task.LoopingCall(self.session.post_session_stats)
self.alertmanager.register_handler(
'session_stats_alert', self._on_alert_session_stats
'session_stats', self._on_alert_session_stats
)
self.session_rates_timer_interval = 2
self.session_rates_timer = task.LoopingCall(self._update_session_rates)
@ -992,31 +991,33 @@ class Core(component.Component):
path,
tracker,
piece_length,
comment,
target,
webseeds,
private,
created_by,
trackers,
add_to_session,
comment=None,
target=None,
webseeds=None,
private=False,
created_by=None,
trackers=None,
add_to_session=False,
torrent_format=metafile.TorrentFormat.V1,
):
if isinstance(torrent_format, str):
torrent_format = metafile.TorrentFormat(torrent_format)
log.debug('creating torrent..')
threading.Thread(
target=self._create_torrent_thread,
args=(
path,
tracker,
piece_length,
comment,
target,
webseeds,
private,
created_by,
trackers,
add_to_session,
),
).start()
return threads.deferToThread(
self._create_torrent_thread,
path,
tracker,
piece_length,
comment=comment,
target=target,
webseeds=webseeds,
private=private,
created_by=created_by,
trackers=trackers,
add_to_session=add_to_session,
torrent_format=torrent_format,
)
def _create_torrent_thread(
self,
@ -1030,27 +1031,41 @@ class Core(component.Component):
created_by,
trackers,
add_to_session,
torrent_format,
):
from deluge import metafile
metafile.make_meta_file(
filecontent = metafile.make_meta_file_content(
path,
tracker,
piece_length,
comment=comment,
target=target,
webseeds=webseeds,
private=private,
created_by=created_by,
trackers=trackers,
torrent_format=torrent_format,
)
write_file = False
if target or not add_to_session:
write_file = True
if not target:
target = metafile.default_meta_file_path(path)
filename = os.path.split(target)[-1]
if write_file:
with open(target, 'wb') as _file:
_file.write(filecontent)
filedump = b64encode(filecontent)
log.debug('torrent created!')
if add_to_session:
options = {}
options['download_location'] = os.path.split(path)[0]
with open(target, 'rb') as _file:
filedump = b64encode(_file.read())
self.add_torrent_file(os.path.split(target)[1], filedump, options)
self.add_torrent_file(filename, filedump, options)
return filename, filedump
@export
def upload_plugin(self, filename: str, filedump: Union[str, bytes]) -> None:

View file

@ -7,6 +7,7 @@
#
"""The Deluge daemon"""
import logging
import os
import socket

View file

@ -8,6 +8,7 @@
"""PluginManager for Core"""
import logging
from twisted.internet import defer

View file

@ -7,6 +7,7 @@
#
"""RPCServer Module"""
import logging
import os
import sys
@ -46,13 +47,11 @@ TCallable = TypeVar('TCallable', bound=Callable)
@overload
def export(func: TCallable) -> TCallable:
...
def export(func: TCallable) -> TCallable: ...
@overload
def export(auth_level: int) -> Callable[[TCallable], TCallable]:
...
def export(auth_level: int) -> Callable[[TCallable], TCallable]: ...
def export(auth_level=AUTH_LEVEL_DEFAULT):
@ -274,9 +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()
@ -545,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

View file

@ -1138,9 +1138,8 @@ class Torrent:
'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
),
# 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'],
@ -1544,20 +1543,18 @@ class Torrent:
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

View file

@ -7,6 +7,7 @@
#
"""TorrentManager handles Torrent objects"""
import datetime
import logging
import operator
@ -50,10 +51,10 @@ from deluge.event import (
log = logging.getLogger(__name__)
LT_DEFAULT_ADD_TORRENT_FLAGS = (
lt.add_torrent_params_flags_t.flag_paused
| lt.add_torrent_params_flags_t.flag_auto_managed
| lt.add_torrent_params_flags_t.flag_update_subscribe
| lt.add_torrent_params_flags_t.flag_apply_ip_filter
lt.torrent_flags.paused
| lt.torrent_flags.auto_managed
| lt.torrent_flags.update_subscribe
| lt.torrent_flags.apply_ip_filter
)
@ -202,34 +203,32 @@ class TorrentManager(component.Component):
# Register alert functions
alert_handles = [
'external_ip_alert',
'performance_alert',
'add_torrent_alert',
'metadata_received_alert',
'torrent_finished_alert',
'torrent_paused_alert',
'torrent_checked_alert',
'torrent_resumed_alert',
'tracker_reply_alert',
'tracker_announce_alert',
'tracker_warning_alert',
'tracker_error_alert',
'file_renamed_alert',
'file_error_alert',
'file_completed_alert',
'storage_moved_alert',
'storage_moved_failed_alert',
'state_update_alert',
'state_changed_alert',
'save_resume_data_alert',
'save_resume_data_failed_alert',
'fastresume_rejected_alert',
'external_ip',
'performance',
'add_torrent',
'metadata_received',
'torrent_finished',
'torrent_paused',
'torrent_checked',
'torrent_resumed',
'tracker_reply',
'tracker_announce',
'tracker_warning',
'tracker_error',
'file_renamed',
'file_error',
'file_completed',
'storage_moved',
'storage_moved_failed',
'state_update',
'state_changed',
'save_resume_data',
'save_resume_data_failed',
'fastresume_rejected',
]
for alert_handle in alert_handles:
on_alert_func = getattr(
self, ''.join(['on_alert_', alert_handle.replace('_alert', '')])
)
on_alert_func = getattr(self, ''.join(['on_alert_', alert_handle]))
self.alerts.register_handler(alert_handle, on_alert_func)
# Define timers
@ -292,8 +291,8 @@ class TorrentManager(component.Component):
if torrent.options['remove_at_ratio']:
self.remove(torrent_id)
break
if not torrent.status.paused:
torrent.pause()
torrent.pause()
def __getitem__(self, torrent_id):
"""Return the Torrent with torrent_id.
@ -369,11 +368,11 @@ class TorrentManager(component.Component):
add_torrent_params.flags = (
(
LT_DEFAULT_ADD_TORRENT_FLAGS
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
| lt.add_torrent_params_flags_t.flag_upload_mode
| lt.torrent_flags.duplicate_is_error
| lt.torrent_flags.upload_mode
)
^ lt.add_torrent_params_flags_t.flag_auto_managed
^ lt.add_torrent_params_flags_t.flag_paused
^ lt.torrent_flags.auto_managed
^ lt.torrent_flags.paused
)
torrent_handle = self.session.add_torrent(add_torrent_params)
@ -481,16 +480,12 @@ class TorrentManager(component.Component):
# Set flags: enable duplicate_is_error & override_resume_data, disable auto_managed.
add_torrent_params['flags'] = (
LT_DEFAULT_ADD_TORRENT_FLAGS
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
| lt.add_torrent_params_flags_t.flag_override_resume_data
) ^ lt.add_torrent_params_flags_t.flag_auto_managed
LT_DEFAULT_ADD_TORRENT_FLAGS | lt.torrent_flags.duplicate_is_error
) ^ lt.torrent_flags.auto_managed
if options['seed_mode']:
add_torrent_params['flags'] |= lt.add_torrent_params_flags_t.flag_seed_mode
add_torrent_params['flags'] |= lt.torrent_flags.seed_mode
if options['super_seeding']:
add_torrent_params[
'flags'
] |= lt.add_torrent_params_flags_t.flag_super_seeding
add_torrent_params['flags'] |= lt.torrent_flags.super_seeding
return torrent_id, add_torrent_params

View file

@ -166,7 +166,8 @@ def deprecated(func):
class CoroutineDeferred(defer.Deferred):
"""Wraps a coroutine in a Deferred.
It will dynamically pass through the underlying coroutine without wrapping where apporpriate."""
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
@ -195,17 +196,33 @@ class CoroutineDeferred(defer.Deferred):
d = defer.ensureDeferred(self.coro)
d.chainDeferred(self)
def addCallbacks(self, *args, **kwargs): # noqa: N802
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]]
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."""

View file

@ -13,6 +13,7 @@ This module describes the types of events that can be generated by the daemon
and subsequently emitted to the clients.
"""
known_events = {}

View file

@ -6,7 +6,7 @@
# See LICENSE for more details.
#
import cgi
import email.message
import logging
import os.path
import zlib
@ -133,9 +133,10 @@ class HTTPDownloaderAgent:
content_disp = headers.getRawHeaders(b'content-disposition')[0].decode(
'utf-8'
)
content_disp_params = cgi.parse_header(content_disp)[1]
if 'filename' in content_disp_params:
new_file_name = content_disp_params['filename']
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
@ -152,7 +153,10 @@ class HTTPDownloaderAgent:
self.filename = new_file_name
cont_type_header = headers.getRawHeaders(b'content-type')[0].decode()
cont_type, params = cgi.parse_header(cont_type_header)
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/'):

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

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

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

File diff suppressed because it is too large Load diff

6214
deluge/i18n/mo.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

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

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