summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/cd.yml100
-rw-r--r--.github/workflows/ci.yml64
-rw-r--r--.gitignore1
-rw-r--r--.pre-commit-config.yaml29
-rw-r--r--.pylintrc7
-rw-r--r--CHANGELOG.md84
-rw-r--r--DEPENDS.md11
-rw-r--r--README.md1
-rw-r--r--deluge/_libtorrent.py7
-rw-r--r--deluge/argparserbase.py19
-rw-r--r--deluge/bencode.py14
-rw-r--r--deluge/common.py268
-rw-r--r--deluge/component.py22
-rw-r--r--deluge/config.py178
-rw-r--r--deluge/configmanager.py5
-rw-r--r--deluge/conftest.py192
-rw-r--r--deluge/core/alertmanager.py14
-rw-r--r--deluge/core/authmanager.py26
-rw-r--r--deluge/core/core.py448
-rw-r--r--deluge/core/daemon.py13
-rw-r--r--deluge/core/daemon_entry.py3
-rw-r--r--deluge/core/eventmanager.py3
-rw-r--r--deluge/core/filtermanager.py7
-rw-r--r--deluge/core/pluginmanager.py3
-rw-r--r--deluge/core/preferencesmanager.py34
-rw-r--r--deluge/core/rpcserver.py80
-rw-r--r--deluge/core/torrent.py148
-rw-r--r--deluge/core/torrentmanager.py160
-rw-r--r--deluge/crypto_utils.py61
-rw-r--r--deluge/decorators.py66
-rw-r--r--deluge/error.py18
-rw-r--r--deluge/event.py9
-rw-r--r--deluge/httpdownloader.py20
-rw-r--r--deluge/i18n/languages.py3
-rw-r--r--deluge/i18n/util.py16
-rw-r--r--deluge/log.py45
-rw-r--r--deluge/maketorrent.py5
-rw-r--r--deluge/metafile.py5
-rw-r--r--deluge/path_chooser_common.py12
-rw-r--r--deluge/pluginmanagerbase.py49
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/__init__.py9
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/common.py3
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/core.py20
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js31
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js5
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui20
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py9
-rw-r--r--deluge/plugins/AutoAdd/deluge_autoadd/webui.py3
-rw-r--r--deluge/plugins/AutoAdd/setup.py1
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/__init__.py9
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/common.py14
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/core.py10
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js2
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui6
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/decompressers.py3
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/detect.py3
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/gtkui.py5
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py10
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/readers.py5
-rw-r--r--deluge/plugins/Blocklist/deluge_blocklist/webui.py3
-rw-r--r--deluge/plugins/Blocklist/setup.py1
-rw-r--r--deluge/plugins/Execute/deluge_execute/__init__.py9
-rw-r--r--deluge/plugins/Execute/deluge_execute/common.py3
-rw-r--r--deluge/plugins/Execute/deluge_execute/core.py3
-rw-r--r--deluge/plugins/Execute/deluge_execute/data/execute_prefs.ui2
-rw-r--r--deluge/plugins/Execute/deluge_execute/gtkui.py7
-rw-r--r--deluge/plugins/Execute/deluge_execute/webui.py3
-rw-r--r--deluge/plugins/Execute/setup.py1
-rw-r--r--deluge/plugins/Extractor/deluge_extractor/__init__.py9
-rw-r--r--deluge/plugins/Extractor/deluge_extractor/common.py3
-rw-r--r--deluge/plugins/Extractor/deluge_extractor/core.py10
-rw-r--r--deluge/plugins/Extractor/deluge_extractor/data/extractor_prefs.ui2
-rw-r--r--deluge/plugins/Extractor/deluge_extractor/gtkui.py5
-rw-r--r--deluge/plugins/Extractor/deluge_extractor/webui.py3
-rw-r--r--deluge/plugins/Extractor/setup.py1
-rw-r--r--deluge/plugins/Label/deluge_label/__init__.py9
-rw-r--r--deluge/plugins/Label/deluge_label/common.py3
-rw-r--r--deluge/plugins/Label/deluge_label/core.py3
-rw-r--r--deluge/plugins/Label/deluge_label/data/label.js20
-rw-r--r--deluge/plugins/Label/deluge_label/data/label_add.ui2
-rw-r--r--deluge/plugins/Label/deluge_label/data/label_options.ui12
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/__init__.py3
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/label_config.py5
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py13
-rw-r--r--deluge/plugins/Label/deluge_label/gtkui/submenu.py3
-rw-r--r--deluge/plugins/Label/deluge_label/test.py3
-rw-r--r--deluge/plugins/Label/deluge_label/webui.py3
-rw-r--r--deluge/plugins/Label/setup.py1
-rw-r--r--deluge/plugins/Notifications/deluge_notifications/__init__.py9
-rw-r--r--deluge/plugins/Notifications/deluge_notifications/common.py5
-rw-r--r--deluge/plugins/Notifications/deluge_notifications/core.py6
-rw-r--r--deluge/plugins/Notifications/deluge_notifications/data/config.ui10
-rw-r--r--deluge/plugins/Notifications/deluge_notifications/gtkui.py3
-rw-r--r--deluge/plugins/Notifications/deluge_notifications/test.py11
-rw-r--r--deluge/plugins/Notifications/deluge_notifications/webui.py3
-rwxr-xr-xdeluge/plugins/Notifications/setup.py1
-rw-r--r--deluge/plugins/Scheduler/deluge_scheduler/__init__.py9
-rw-r--r--deluge/plugins/Scheduler/deluge_scheduler/common.py3
-rw-r--r--deluge/plugins/Scheduler/deluge_scheduler/core.py3
-rw-r--r--deluge/plugins/Scheduler/deluge_scheduler/gtkui.py5
-rw-r--r--deluge/plugins/Scheduler/deluge_scheduler/webui.py3
-rw-r--r--deluge/plugins/Scheduler/setup.py1
-rw-r--r--deluge/plugins/Stats/deluge_stats/__init__.py9
-rw-r--r--deluge/plugins/Stats/deluge_stats/common.py3
-rw-r--r--deluge/plugins/Stats/deluge_stats/core.py3
-rw-r--r--deluge/plugins/Stats/deluge_stats/graph.py9
-rw-r--r--deluge/plugins/Stats/deluge_stats/gtkui.py9
-rw-r--r--deluge/plugins/Stats/deluge_stats/tests/test_stats.py43
-rw-r--r--deluge/plugins/Stats/deluge_stats/webui.py3
-rw-r--r--deluge/plugins/Stats/setup.py1
-rw-r--r--deluge/plugins/Toggle/deluge_toggle/__init__.py9
-rw-r--r--deluge/plugins/Toggle/deluge_toggle/common.py3
-rw-r--r--deluge/plugins/Toggle/deluge_toggle/core.py3
-rw-r--r--deluge/plugins/Toggle/deluge_toggle/gtkui.py3
-rw-r--r--deluge/plugins/Toggle/deluge_toggle/webui.py3
-rw-r--r--deluge/plugins/Toggle/setup.py1
-rw-r--r--deluge/plugins/WebUi/deluge_webui/__init__.py9
-rw-r--r--deluge/plugins/WebUi/deluge_webui/common.py3
-rw-r--r--deluge/plugins/WebUi/deluge_webui/core.py3
-rw-r--r--deluge/plugins/WebUi/deluge_webui/data/config.ui2
-rw-r--r--deluge/plugins/WebUi/deluge_webui/gtkui.py3
-rw-r--r--deluge/plugins/WebUi/deluge_webui/tests/test_plugin_webui.py27
-rw-r--r--deluge/plugins/WebUi/setup.py1
-rw-r--r--deluge/plugins/init.py5
-rw-r--r--deluge/plugins/pluginbase.py19
-rw-r--r--deluge/scripts/create_plugin.py37
-rw-r--r--deluge/scripts/deluge_remote.py3
-rw-r--r--deluge/tests/__init__.py2
-rw-r--r--deluge/tests/basetest.py59
-rw-r--r--deluge/tests/common.py70
-rw-r--r--deluge/tests/common_web.py22
-rw-r--r--deluge/tests/daemon_base.py19
-rw-r--r--deluge/tests/data/seo.icobin1150 -> 0 bytes
-rw-r--r--deluge/tests/data/seo.svg1
-rw-r--r--deluge/tests/test_alertmanager.py12
-rw-r--r--deluge/tests/test_authmanager.py10
-rw-r--r--deluge/tests/test_bencode.py18
-rw-r--r--deluge/tests/test_client.py66
-rw-r--r--deluge/tests/test_common.py212
-rw-r--r--deluge/tests/test_component.py122
-rw-r--r--deluge/tests/test_config.py159
-rw-r--r--deluge/tests/test_core.py242
-rw-r--r--deluge/tests/test_decorators.py20
-rw-r--r--deluge/tests/test_error.py33
-rw-r--r--deluge/tests/test_files_tab.py66
-rw-r--r--deluge/tests/test_httpdownloader.py198
-rw-r--r--deluge/tests/test_json_api.py179
-rw-r--r--deluge/tests/test_log.py10
-rw-r--r--deluge/tests/test_maketorrent.py33
-rw-r--r--deluge/tests/test_maybe_coroutine.py213
-rw-r--r--deluge/tests/test_metafile.py27
-rw-r--r--deluge/tests/test_plugin_metadata.py36
-rw-r--r--deluge/tests/test_rpcserver.py46
-rw-r--r--deluge/tests/test_security.py75
-rw-r--r--deluge/tests/test_sessionproxy.py56
-rw-r--r--deluge/tests/test_torrent.py111
-rw-r--r--deluge/tests/test_torrentmanager.py116
-rw-r--r--deluge/tests/test_torrentview.py170
-rw-r--r--deluge/tests/test_tracker_icons.py80
-rw-r--r--deluge/tests/test_transfer.py49
-rw-r--r--deluge/tests/test_ui_common.py42
-rw-r--r--deluge/tests/test_ui_console.py71
-rw-r--r--deluge/tests/test_ui_entry.py324
-rw-r--r--deluge/tests/test_ui_gtk3.py22
-rw-r--r--deluge/tests/test_web_api.py115
-rw-r--r--deluge/tests/test_web_auth.py11
-rw-r--r--deluge/tests/test_webserver.py23
-rw-r--r--deluge/tests/twisted/plugins/delugereporter.py50
-rw-r--r--deluge/transfer.py7
-rw-r--r--deluge/ui/client.py13
-rw-r--r--deluge/ui/common.py11
-rw-r--r--deluge/ui/console/__init__.py5
-rw-r--r--deluge/ui/console/cmdline/command.py7
-rw-r--r--deluge/ui/console/cmdline/commands/__init__.py3
-rw-r--r--deluge/ui/console/cmdline/commands/add.py13
-rw-r--r--deluge/ui/console/cmdline/commands/cache.py5
-rw-r--r--deluge/ui/console/cmdline/commands/config.py9
-rw-r--r--deluge/ui/console/cmdline/commands/connect.py12
-rw-r--r--deluge/ui/console/cmdline/commands/debug.py3
-rw-r--r--deluge/ui/console/cmdline/commands/gui.py3
-rw-r--r--deluge/ui/console/cmdline/commands/halt.py3
-rw-r--r--deluge/ui/console/cmdline/commands/help.py3
-rw-r--r--deluge/ui/console/cmdline/commands/info.py40
-rw-r--r--deluge/ui/console/cmdline/commands/manage.py9
-rw-r--r--deluge/ui/console/cmdline/commands/move.py5
-rw-r--r--deluge/ui/console/cmdline/commands/pause.py3
-rw-r--r--deluge/ui/console/cmdline/commands/plugin.py3
-rw-r--r--deluge/ui/console/cmdline/commands/quit.py3
-rw-r--r--deluge/ui/console/cmdline/commands/recheck.py3
-rw-r--r--deluge/ui/console/cmdline/commands/resume.py3
-rw-r--r--deluge/ui/console/cmdline/commands/rm.py5
-rw-r--r--deluge/ui/console/cmdline/commands/status.py12
-rw-r--r--deluge/ui/console/cmdline/commands/update_tracker.py3
-rw-r--r--deluge/ui/console/console.py11
-rw-r--r--deluge/ui/console/main.py13
-rw-r--r--deluge/ui/console/modes/add_util.py5
-rw-r--r--deluge/ui/console/modes/addtorrents.py15
-rw-r--r--deluge/ui/console/modes/basemode.py11
-rw-r--r--deluge/ui/console/modes/cmdline.py23
-rw-r--r--deluge/ui/console/modes/connectionmanager.py36
-rw-r--r--deluge/ui/console/modes/eventview.py7
-rw-r--r--deluge/ui/console/modes/preferences/__init__.py2
-rw-r--r--deluge/ui/console/modes/preferences/preference_panes.py10
-rw-r--r--deluge/ui/console/modes/preferences/preferences.py5
-rw-r--r--deluge/ui/console/modes/torrentdetail.py37
-rw-r--r--deluge/ui/console/modes/torrentlist/__init__.py5
-rw-r--r--deluge/ui/console/modes/torrentlist/add_torrents_popup.py5
-rw-r--r--deluge/ui/console/modes/torrentlist/filtersidebar.py3
-rw-r--r--deluge/ui/console/modes/torrentlist/queue_mode.py5
-rw-r--r--deluge/ui/console/modes/torrentlist/search_mode.py8
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentactions.py7
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentlist.py9
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentview.py11
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentviewcolumns.py3
-rw-r--r--deluge/ui/console/parser.py13
-rw-r--r--deluge/ui/console/utils/colors.py11
-rw-r--r--deluge/ui/console/utils/column.py3
-rw-r--r--deluge/ui/console/utils/common.py3
-rw-r--r--deluge/ui/console/utils/curses_util.py7
-rw-r--r--deluge/ui/console/utils/format_utils.py19
-rw-r--r--deluge/ui/console/widgets/__init__.py2
-rw-r--r--deluge/ui/console/widgets/fields.py42
-rw-r--r--deluge/ui/console/widgets/inputpane.py3
-rw-r--r--deluge/ui/console/widgets/popup.py7
-rw-r--r--deluge/ui/console/widgets/sidebar.py3
-rw-r--r--deluge/ui/console/widgets/statusbars.py20
-rw-r--r--deluge/ui/console/widgets/window.py5
-rw-r--r--deluge/ui/coreconfig.py3
-rw-r--r--deluge/ui/countries.py3
-rw-r--r--deluge/ui/data/share/applications/deluge.desktop.in1
-rw-r--r--deluge/ui/gtk3/__init__.py7
-rw-r--r--deluge/ui/gtk3/aboutdialog.py7
-rw-r--r--deluge/ui/gtk3/addtorrentdialog.py11
-rw-r--r--deluge/ui/gtk3/common.py76
-rw-r--r--deluge/ui/gtk3/connectionmanager.py14
-rw-r--r--deluge/ui/gtk3/createtorrentdialog.py5
-rw-r--r--deluge/ui/gtk3/details_tab.py7
-rw-r--r--deluge/ui/gtk3/dialogs.py121
-rw-r--r--deluge/ui/gtk3/edittrackersdialog.py55
-rw-r--r--deluge/ui/gtk3/files_tab.py7
-rw-r--r--deluge/ui/gtk3/filtertreeview.py9
-rw-r--r--deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui2
-rw-r--r--deluge/ui/gtk3/glade/add_torrent_dialog.ui8
-rw-r--r--deluge/ui/gtk3/glade/add_torrent_dialog.url.ui2
-rw-r--r--deluge/ui/gtk3/glade/connect_peer_dialog.ui2
-rw-r--r--deluge/ui/gtk3/glade/connection_manager.addhost.ui137
-rw-r--r--deluge/ui/gtk3/glade/connection_manager.ui2
-rw-r--r--deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui2
-rw-r--r--deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui2
-rw-r--r--deluge/ui/gtk3/glade/create_torrent_dialog.ui4
-rw-r--r--deluge/ui/gtk3/glade/edit_trackers.edit.ui2
-rw-r--r--deluge/ui/gtk3/glade/main_window.tabs.ui10
-rw-r--r--deluge/ui/gtk3/glade/other_dialog.ui2
-rw-r--r--deluge/ui/gtk3/glade/path_combo_chooser.ui3
-rw-r--r--deluge/ui/gtk3/glade/preferences_dialog.ui95
-rw-r--r--deluge/ui/gtk3/glade/torrent_menu.ui16
-rw-r--r--deluge/ui/gtk3/gtkui.py11
-rw-r--r--deluge/ui/gtk3/ipcinterface.py13
-rw-r--r--deluge/ui/gtk3/listview.py19
-rw-r--r--deluge/ui/gtk3/mainwindow.py9
-rw-r--r--deluge/ui/gtk3/menubar.py36
-rw-r--r--deluge/ui/gtk3/menubar_osx.py3
-rw-r--r--deluge/ui/gtk3/new_release_dialog.py5
-rw-r--r--deluge/ui/gtk3/options_tab.py10
-rw-r--r--deluge/ui/gtk3/path_chooser.py5
-rwxr-xr-xdeluge/ui/gtk3/path_combo_chooser.py39
-rw-r--r--deluge/ui/gtk3/peers_tab.py13
-rw-r--r--deluge/ui/gtk3/piecesbar.py14
-rw-r--r--deluge/ui/gtk3/pluginmanager.py3
-rw-r--r--deluge/ui/gtk3/preferences.py103
-rw-r--r--deluge/ui/gtk3/queuedtorrents.py3
-rw-r--r--deluge/ui/gtk3/removetorrentdialog.py5
-rw-r--r--deluge/ui/gtk3/sidebar.py3
-rw-r--r--deluge/ui/gtk3/status_tab.py5
-rw-r--r--deluge/ui/gtk3/statusbar.py27
-rw-r--r--deluge/ui/gtk3/systemtray.py9
-rw-r--r--deluge/ui/gtk3/tab_data_funcs.py11
-rw-r--r--deluge/ui/gtk3/toolbar.py3
-rw-r--r--deluge/ui/gtk3/torrentdetails.py5
-rw-r--r--deluge/ui/gtk3/torrentview.py13
-rw-r--r--deluge/ui/gtk3/torrentview_data_funcs.py16
-rw-r--r--deluge/ui/gtk3/trackers_tab.py7
-rw-r--r--deluge/ui/hostlist.py69
-rw-r--r--deluge/ui/sessionproxy.py19
-rw-r--r--deluge/ui/tracker_icons.py139
-rw-r--r--deluge/ui/ui.py8
-rw-r--r--deluge/ui/ui_entry.py7
-rw-r--r--deluge/ui/web/__init__.py2
-rw-r--r--deluge/ui/web/auth.py5
-rw-r--r--deluge/ui/web/common.py7
-rw-r--r--deluge/ui/web/css/deluge.css2
-rw-r--r--deluge/ui/web/js/deluge-all/AboutWindow.js3
-rw-r--r--deluge/ui/web/js/deluge-all/AddTrackerWindow.js3
-rw-r--r--deluge/ui/web/js/deluge-all/Deluge.js5
-rw-r--r--deluge/ui/web/js/deluge-all/EditTrackersWindow.js1
-rw-r--r--deluge/ui/web/js/deluge-all/FilterPanel.js2
-rw-r--r--deluge/ui/web/js/deluge-all/Formatters.js301
-rw-r--r--deluge/ui/web/js/deluge-all/TorrentGrid.js14
-rw-r--r--deluge/ui/web/js/deluge-all/add/AddWindow.js18
-rw-r--r--deluge/ui/web/js/deluge-all/add/FilesTab.js1
-rw-r--r--deluge/ui/web/js/deluge-all/add/OptionsPanel.js5
-rw-r--r--deluge/ui/web/js/deluge-all/details/DetailsTab.js4
-rw-r--r--deluge/ui/web/js/deluge-all/details/FilesTab.js1
-rw-r--r--deluge/ui/web/js/deluge-all/details/PeersTab.js2
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js3
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js1
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js5
-rw-r--r--deluge/ui/web/json_api.py56
-rw-r--r--deluge/ui/web/pluginmanager.py28
-rw-r--r--deluge/ui/web/server.py8
-rw-r--r--deluge/ui/web/web.py7
-rw-r--r--docs/requirements.txt5
-rw-r--r--docs/source/conf.py38
-rw-r--r--docs/source/contributing/testing.md28
-rw-r--r--docs/source/devguide/how-to/update-1.3-plugin.md15
-rw-r--r--docs/source/intro/01-install.md15
-rwxr-xr-xgen_web_gettext.py5
-rwxr-xr-xgenerate_pot.py5
-rwxr-xr-xminify_web_js.py14
-rwxr-xr-xmsgfmt.py19
-rwxr-xr-xpackaging/source/make_release.py10
-rw-r--r--packaging/systemd/deluge-web.service2
-rw-r--r--packaging/systemd/user/deluge-web.service16
-rw-r--r--packaging/systemd/user/deluged.service13
-rw-r--r--packaging/win/README.md34
-rw-r--r--packaging/win/deluge-win-installer.nsi (renamed from packaging/win32/deluge-win32-installer.nsi)69
-rw-r--r--packaging/win/delugewin.spec183
-rw-r--r--packaging/win/installer-side.bmp (renamed from packaging/win32/installer-side.bmp)bin206038 -> 206038 bytes
-rw-r--r--packaging/win/installer-top.bmp (renamed from packaging/win32/installer-top.bmp)bin34254 -> 34254 bytes
-rw-r--r--packaging/win/pyi_rth_gtk_csd.py3
-rw-r--r--packaging/win/setup_nsis.py49
-rw-r--r--packaging/win32/DelugeStart Theme/etc/gtk-2.0/gtkrc25
-rw-r--r--packaging/win32/DelugeStart Theme/lib/gtk-2.0/2.10.0/engines/libmurrine.dllbin156686 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check1.pngbin112 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check2.pngbin200 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check3.pngbin112 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check4.pngbin196 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check5.pngbin183 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/checklight.pngbin81 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option1.pngbin121 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option2.pngbin168 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option3.pngbin124 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option4.pngbin171 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Icons/close.pngbin198 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-horiz.pngbin82 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-vert.pngbin82 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-insens.pngbin84 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-prelight.pngbin125 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz.pngbin127 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-insens.pngbin84 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-prelight.pngbin128 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert.pngbin132 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-horiz.pngbin86 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-vert.pngbin84 -> 0 bytes
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/gtkrc519
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/checkradiobutton179
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/menu-menubar50
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/notebook29
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/scrollbar125
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/tooltips21
-rw-r--r--packaging/win32/DelugeStart Theme/share/themes/DelugeStart/index.theme12
-rw-r--r--packaging/win32/Win32 README.txt22
-rw-r--r--packaging/win32/deluge-bbfreeze.py264
-rw-r--r--pyproject.toml2
-rw-r--r--requirements-tests.txt2
-rw-r--r--requirements.txt7
-rw-r--r--setup.cfg22
-rwxr-xr-xsetup.py42
-rw-r--r--tox.ini8
-rwxr-xr-xversion.py7
371 files changed, 4355 insertions, 5830 deletions
diff --git a/.gitattributes b/.gitattributes
index b73f27d2f..d3e940016 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -3,3 +3,4 @@
.gitignore export-ignore
*.py diff=python
ext-all.js diff=minjs
+*.state -merge -text
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 000000000..fe15ad4f0
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,100 @@
+name: Package
+
+on:
+ push:
+ tags:
+ - "deluge-*"
+ - "!deluge*-dev*"
+ branches:
+ - develop
+ pull_request:
+ types: [labeled, opened, synchronize, reopened]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: "Enter a tag or commit to package"
+ default: ""
+
+jobs:
+ windows_package:
+ runs-on: windows-2019
+ if: (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'package'))
+ strategy:
+ matrix:
+ arch: [x64, x86]
+ python: ["3.9"]
+ libtorrent: [2.0.6, 1.2.15]
+
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ # Checkout Deluge source to subdir to enable packaging any tag/commit
+ - name: Checkout Deluge source
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.inputs.ref }}
+ fetch-depth: 0
+ path: deluge_src
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python}}
+ architecture: ${{ matrix.arch }}
+ cache: pip
+
+ - name: Prepare pip
+ run: python -m pip install wheel
+
+ - name: Install GTK
+ run: |
+ $WebClient = New-Object System.Net.WebClient
+ $WebClient.DownloadFile("https://github.com/deluge-torrent/gvsbuild-release/releases/download/latest/gvsbuild-py${{ matrix.python }}-vs16-${{ matrix.arch }}.zip","C:\GTK.zip")
+ 7z x C:\GTK.zip -oc:\GTK
+ echo "C:\GTK\release\lib" | Out-File -FilePath $env:GITHUB_PATH -Append
+ echo "C:\GTK\release\bin" | Out-File -FilePath $env:GITHUB_PATH -Append
+ echo "C:\GTK\release" | Out-File -FilePath $env:GITHUB_PATH -Append
+ python -m pip install --no-index --find-links="C:\GTK\release\python" pycairo PyGObject
+
+ - name: Install Python dependencies
+ run: >
+ python -m pip install
+ twisted[tls]==22.4.0
+ libtorrent==${{ matrix.libtorrent }}
+ pyinstaller==4.10
+ pygame
+ -r requirements.txt
+
+ - name: Install Deluge
+ working-directory: deluge_src
+ run: |
+ python -m pip install .
+ python setup.py install_scripts
+
+ - name: Freeze Deluge
+ working-directory: packaging/win
+ run: |
+ pyinstaller --clean delugewin.spec --distpath freeze
+
+ - name: Fix OpenSSL for libtorrent x64
+ if: ${{ matrix.arch == 'x64' }}
+ working-directory: packaging/win/freeze/Deluge
+ run: |
+ cp libssl-1_1.dll libssl-1_1-x64.dll
+ cp libcrypto-1_1.dll libcrypto-1_1-x64.dll
+
+ - name: Make Deluge Installer
+ working-directory: ./packaging/win
+ run: |
+ python setup_nsis.py
+ makensis /Darch=${{ matrix.arch }} deluge-win-installer.nsi
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: deluge-py${{ matrix.python }}-lt${{ matrix.libtorrent }}-${{ matrix.arch }}
+ path: packaging/win/*.exe
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b3c7dc990..82a9fd99a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,6 +10,9 @@ on:
jobs:
test-linux:
runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ python-version: ["3.7", "3.10"]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
@@ -20,26 +23,13 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: "3.8"
+ python-version: ${{ matrix.python-version }}
+ cache: "pip"
+ cache-dependency-path: "requirements*.txt"
- - name: Cache pip
- uses: actions/cache@v2
- with:
- path: ~/.cache/pip
- # Look to see if there is a cache hit for the corresponding requirements file
- key: ${{ runner.os }}-pip-${{ hashFiles('tox.ini', 'setup.py', 'requirements*.txt') }}
- restore-keys: |
- ${{ runner.os }}-pip-
- ${{ runner.os }}-
-
- - name: Add libtorrent deb repository
- uses: myci-actions/add-deb-repo@8
- with:
- repo: deb http://ppa.launchpad.net/libtorrent.org/1.2-daily/ubuntu focal main
- repo-name: libtorrent
- keys: 58E5430D9667FAEFFCA0B93F32309D6B9E009EDB
- key-server: keyserver.ubuntu.com
- install: python3-libtorrent-dbg
+ - name: Sets env var for security
+ if: (github.event_name == 'pull_request' && contains(github.event.pull_request.body, 'security_test')) || (github.event_name == 'push' && contains(github.event.head_commit.message, 'security_test'))
+ run: echo "SECURITY_TESTS=True" >> $GITHUB_ENV
- name: Install dependencies
run: |
@@ -47,6 +37,15 @@ jobs:
pip install -r requirements.txt -r requirements-tests.txt
pip install -e .
+ - name: Install security dependencies
+ if: contains(env.SECURITY_TESTS, 'True')
+ run: |
+ wget -O- $TESTSSL_URL$TESTSSL_VER | tar xz
+ mv -t deluge/tests/data testssl.sh-$TESTSSL_VER/testssl.sh testssl.sh-$TESTSSL_VER/etc/;
+ env:
+ TESTSSL_VER: 3.0.6
+ TESTSSL_URL: https://codeload.github.com/drwetter/testssl.sh/tar.gz/refs/tags/v
+
- name: Setup core dump directory
run: |
sudo mkdir /cores/ && sudo chmod 777 /cores/
@@ -55,9 +54,8 @@ jobs:
- name: Test with pytest
run: |
ulimit -c unlimited # Enable core dumps to be captured
- cp /usr/lib/python3/dist-packages/libtorrent*.so $GITHUB_WORKSPACE/deluge
python -c 'from deluge._libtorrent import lt; print(lt.__version__)';
- catchsegv python -X dev -m pytest -v -m "not (todo or gtkui or security)" deluge
+ catchsegv python -X dev -m pytest -v -m "not (todo or gtkui)" deluge
- uses: actions/upload-artifact@v2
# capture all crashes as build artifacts
@@ -67,7 +65,10 @@ jobs:
path: /cores
test-windows:
- runs-on: windows-latest
+ runs-on: windows-2019
+ strategy:
+ matrix:
+ python-version: ["3.7", "3.10"]
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
@@ -78,26 +79,17 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: "3.7"
-
- - name: Cache pip
- uses: actions/cache@v2
- with:
- path: '%LOCALAPPDATA%\pip\Cache'
- # Look to see if there is a cache hit for the corresponding requirements file
- key: ${{ runner.os }}-pip-${{ hashFiles('tox.ini', 'setup.py', 'requirements*.txt') }}
- restore-keys: |
- ${{ runner.os }}-pip-
- ${{ runner.os }}-
+ python-version: ${{ matrix.python-version }}
+ cache: "pip"
+ cache-dependency-path: "requirements*.txt"
- name: Install dependencies
run: |
- python -m pip install --upgrade pip wheel
- python -m pip install libtorrent==1.2.*
+ pip install --upgrade pip wheel
pip install -r requirements.txt -r requirements-tests.txt
pip install -e .
- name: Test with pytest
run: |
python -c 'import libtorrent as lt; print(lt.__version__)';
- pytest -m "not (todo or gtkui or security)" deluge
+ pytest -v -m "not (todo or gtkui or security)" deluge
diff --git a/.gitignore b/.gitignore
index 70988729d..15fb2aa88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,7 +10,6 @@ docs/source/modules/deluge*.rst
__pycache__/
*.py[cod]
*.tar.*
-_trial_temp
.tox/
deluge/i18n/*/
deluge.pot
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 133d536dd..5ff4a93d3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,32 +3,35 @@ default_language_version:
exclude: >
(?x)^(
deluge/ui/web/docs/template/.*|
+ deluge/tests/data/.*svg|
)$
repos:
- - repo: https://github.com/ambv/black
- rev: 20.8b1
+ - repo: https://github.com/psf/black
+ rev: 22.3.0
hooks:
- id: black
name: Fmt Black
- repo: https://github.com/pre-commit/mirrors-prettier
- rev: v2.2.1
+ rev: v2.5.1
hooks:
- id: prettier
name: Fmt Prettier
# Workaround to list modified files only.
args: [--list-different]
- - repo: https://gitlab.com/pycqa/flake8
- # v3.7.9 due to E402 issue: https://gitlab.com/pycqa/flake8/-/issues/638
- rev: 3.7.9
+ - 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:
- - flake8-isort==4.0.0
- - pep8-naming==0.11.1
- args: [--isort-show-traceback]
+ - pep8-naming==0.12.1
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v3.4.0
+ rev: v4.1.0
hooks:
- id: double-quote-string-fixer
name: Fix Double-quotes
@@ -40,3 +43,9 @@ repos:
args: [--fix=auto]
- id: trailing-whitespace
name: Fix Trailing whitespace
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v2.31.0
+ hooks:
+ - id: pyupgrade
+ args: [--py36-plus]
+ stages: [manual]
diff --git a/.pylintrc b/.pylintrc
index becbe3f28..75fa0e6e4 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -289,7 +289,7 @@ callbacks=cb_,_cb
# List of qualified module names which can have objects that can redefine
# builtins.
-redefining-builtins-modules=six.moves,future.builtins,future_builtins
+redefining-builtins-modules=
[TYPECHECK]
@@ -359,11 +359,6 @@ known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
-# Analyse import fallback blocks. This can be used to support both Python 2 and
-# 3 compatible code, which means that the block might have code that exists
-# only in one or another interpreter, leading to false positives when analysed.
-analyse-fallback-blocks=no
-
[DESIGN]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c7d500cc..7de306456 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,88 @@
# Changelog
-## 2.0.5 (WIP)
+## 2.1.1 (2022-07-10)
+
+### Core
+
+- Fix missing trackers added via magnet
+- Fix handling magnets with tracker tiers
+
+## 2.1.0 (2022-06-28)
+
+### Breaking changes
+
+- Python 2 support removed (Python >= 3.6)
+- libtorrent minimum requirement increased (>= 1.2).
+
+### Core
+
+- Add support for SVG tracker icons.
+- Fix tracker icon error handling.
+- Fix cleaning-up tracker icon temp files.
+- Fix Plugin manager to handle new metadata 2.1.
+- Hide passwords in config logs.
+- Fix cleaning-up temp files in add_torrent_url.
+- Fix KeyError in sessionproxy after torrent delete.
+- Remove libtorrent deprecated functions.
+- Fix file_completed_alert handling.
+- Add plugin keys to get_torrents_status.
+- Add support for pygeoip dependency.
+- Fix crash logging to Windows protected folder.
+- Add is_interface and is_interface_name to validate network interfaces.
+- Fix is_url and is_infohash error with None value.
+- Fix load_libintl error.
+- Add support for IPv6 in host lists.
+- Add systemd user services.
+- Fix refresh and expire the torrent status cache.
+- Fix crash when logging errors initializing gettext.
+
+### Web UI
+
+- Fix ETA column sorting in correct order (#3413).
+- Fix defining foreground and background colors.
+- Accept charset in content-type for json messages.
+- Fix 'Complete Seen' and 'Completed' sorting.
+- Fix encoding HTML entities for torrent attributes to prevent XSS.
+
+### Gtk UI
+
+- Fix download location textbox width.
+- Fix obscured port number in Connection Manager.
+- Increase connection manager default height.
+- Fix bug with setting move completed in Options tab.
+- Fix adding daemon accounts.
+- Add workaround for crash on Windows with ico or gif icons.
+- Hide account password length in log.
+- Added a torrent menu option for magnet copy.
+- Fix unable to prefetch magnet in thinclient mode.
+- Use GtkSpinner when testing open port.
+- Update About Dialog year.
+- Fix Edit Torrents dialogs close issues.
+- Fix ETA being copied to neighboring empty cells.
+- Disable GTK CSD by default on Windows.
+
+### Console UI
+
+- Fix curses.init_pair raise ValueError on Py3.10.
+- Swap j and k key's behavior to fit vim mode.
+- Fix torrent details status error.
+- Fix incorrect test for when a host is online.
+- Add the torrent label to info command.
+
+### AutoAdd
+
+- Fix handling torrent decode errors.
+- Fix error dialog not being shown on error.
+
+### Blocklist
+
+- Add frequency unit to interval label.
+
+### Notifications
+
+- Fix UnicodeEncodeError upon non-ascii torrent name.
+
+## 2.0.5 (2021-12-15)
### WebUI
diff --git a/DEPENDS.md b/DEPENDS.md
index 0844718c4..41a7ec09f 100644
--- a/DEPENDS.md
+++ b/DEPENDS.md
@@ -7,7 +7,7 @@ All modules will require the [common](#common) section dependencies.
## Prerequisite
-- [Python] _>= 3.5_
+- [Python] _>= 3.6_
## Build
@@ -23,12 +23,12 @@ All modules will require the [common](#common) section dependencies.
- [rencode] _>= 1.0.2_ - Encoding library.
- [PyXDG] - Access freedesktop.org standards for \*nix.
- [xdg-utils] - Provides xdg-open for \*nix.
-- [six]
- [zope.interface]
- [chardet] - Optional: Encoding detection.
- [setproctitle] - Optional: Renaming processes.
- [Pillow] - Optional: Support for resizing tracker icons.
- [dbus-python] - Optional: Show item location in filemanager.
+- [ifaddr] - Optional: Verify network interfaces.
### Linux and BSD
@@ -41,8 +41,8 @@ All modules will require the [common](#common) section dependencies.
## Core (deluged daemon)
-- [libtorrent] _>= 1.1.1_
-- [GeoIP] - Optional: IP address location lookup. (_Debian: `python-geoip`_)
+- [libtorrent] _>= 1.2.0_
+- [GeoIP] or [pygeoip] - Optional: IP address country lookup. (_Debian: `python-geoip`_)
## GTK UI
@@ -81,14 +81,12 @@ All modules will require the [common](#common) section dependencies.
[distro]: https://github.com/nir0s/distro
[pywin32]: https://github.com/mhammond/pywin32
[certifi]: https://pypi.org/project/certifi/
-[py2-ipaddress]: https://pypi.org/project/py2-ipaddress/
[dbus-python]: https://pypi.org/project/dbus-python/
[setproctitle]: https://pypi.org/project/setproctitle/
[gtkosxapplication]: https://github.com/jralls/gtk-mac-integration
[chardet]: https://chardet.github.io/
[rencode]: https://github.com/aresch/rencode
[pyxdg]: https://www.freedesktop.org/wiki/Software/pyxdg/
-[six]: https://pythonhosted.org/six/
[xdg-utils]: https://www.freedesktop.org/wiki/Software/xdg-utils/
[gtk+]: https://www.gtk.org/
[pycairo]: https://cairographics.org/pycairo/
@@ -99,3 +97,4 @@ All modules will require the [common](#common) section dependencies.
[libnotify]: https://developer.gnome.org/libnotify/
[python-appindicator]: https://packages.ubuntu.com/xenial/python-appindicator
[librsvg]: https://wiki.gnome.org/action/show/Projects/LibRsvg
+[ifaddr]: https://pypi.org/project/ifaddr/
diff --git a/README.md b/README.md
index 69d018ef6..a153d21c5 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,7 @@ See the [Thinclient guide] to connect to the daemon from another computer.
- [User guide][user guide]
- [Forum](https://forum.deluge-torrent.org)
- [IRC Libera.Chat #deluge](irc://irc.libera.chat/deluge)
+- [Discord](https://discord.gg/nwaHSE6tqn)
[user guide]: https://dev.deluge-torrent.org/wiki/UserGuide
[thinclient guide]: https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient
diff --git a/deluge/_libtorrent.py b/deluge/_libtorrent.py
index 0020184e5..642855c52 100644
--- a/deluge/_libtorrent.py
+++ b/deluge/_libtorrent.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -15,8 +14,6 @@ Example:
>>> from deluge._libtorrent import lt
"""
-from __future__ import unicode_literals
-
from deluge.common import VersionSplit, get_version
from deluge.error import LibtorrentImportError
@@ -29,10 +26,10 @@ except ImportError:
raise LibtorrentImportError('No libtorrent library found: %s' % (ex))
-REQUIRED_VERSION = '1.1.2.0'
+REQUIRED_VERSION = '1.2.0.0'
LT_VERSION = lt.__version__
if VersionSplit(LT_VERSION) < VersionSplit(REQUIRED_VERSION):
raise LibtorrentImportError(
- 'Deluge %s requires libtorrent >= %s' % (get_version(), REQUIRED_VERSION)
+ f'Deluge {get_version()} requires libtorrent >= {REQUIRED_VERSION}'
)
diff --git a/deluge/argparserbase.py b/deluge/argparserbase.py
index 547c1c71c..5dc433086 100644
--- a/deluge/argparserbase.py
+++ b/deluge/argparserbase.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import argparse
import logging
import os
@@ -95,7 +92,7 @@ def _get_version_detail():
except ImportError:
pass
version_str += 'Python: %s\n' % platform.python_version()
- version_str += 'OS: %s %s\n' % (platform.system(), common.get_os_version())
+ version_str += f'OS: {platform.system()} {common.get_os_version()}\n'
return version_str
@@ -109,8 +106,8 @@ class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
line instead. This way list formatting is not mangled by textwrap.wrap.
"""
wrapped_lines = []
- for l in text.splitlines():
- wrapped_lines.extend(textwrap.wrap(l, width, subsequent_indent=' '))
+ for line in text.splitlines():
+ wrapped_lines.extend(textwrap.wrap(line, width, subsequent_indent=' '))
return wrapped_lines
def _format_action_invocation(self, action):
@@ -137,7 +134,7 @@ class DelugeTextHelpFormatter(argparse.RawDescriptionHelpFormatter):
default = action.dest.upper()
args_string = self._format_args(action, default)
opt = ', '.join(action.option_strings)
- parts.append('%s %s' % (opt, args_string))
+ parts.append(f'{opt} {args_string}')
return ', '.join(parts)
@@ -165,7 +162,7 @@ class ArgParserBase(argparse.ArgumentParser):
self.log_stream = kwargs['log_stream']
del kwargs['log_stream']
- super(ArgParserBase, self).__init__(*args, **kwargs)
+ super().__init__(*args, **kwargs)
self.common_setup = False
self.process_arg_group = False
@@ -202,7 +199,7 @@ class ArgParserBase(argparse.ArgumentParser):
self.group.add_argument(
'-L',
'--loglevel',
- choices=[l for k in deluge.log.levels for l in (k, k.upper())],
+ choices=[level for k in deluge.log.levels for level in (k, k.upper())],
help=_('Set the log level (none, error, warning, info, debug)'),
metavar='<level>',
)
@@ -246,7 +243,7 @@ class ArgParserBase(argparse.ArgumentParser):
argparse.Namespace: The parsed arguments.
"""
- options = super(ArgParserBase, self).parse_args(args=args)
+ options = super().parse_args(args=args)
return self._handle_ui_options(options)
def parse_known_ui_args(self, args, withhold=None):
@@ -262,7 +259,7 @@ class ArgParserBase(argparse.ArgumentParser):
"""
if withhold:
args = [a for a in args if a not in withhold]
- options, remaining = super(ArgParserBase, self).parse_known_args(args=args)
+ options, remaining = super().parse_known_args(args=args)
options.remaining = remaining
# Handle common and process group options
return self._handle_ui_options(options)
diff --git a/deluge/bencode.py b/deluge/bencode.py
index 0c2674b9b..b012ca097 100644
--- a/deluge/bencode.py
+++ b/deluge/bencode.py
@@ -9,13 +9,7 @@
# License.
# Written by Petru Paler
-# Updated by Calum Lind to support both Python 2 and Python 3.
-
-from __future__ import unicode_literals
-
-from sys import version_info
-
-PY2 = version_info.major == 2
+# Updated by Calum Lind to support Python 3.
class BTFailure(Exception):
@@ -90,7 +84,7 @@ def bdecode(x):
return r
-class Bencached(object):
+class Bencached:
__slots__ = ['bencoded']
@@ -146,10 +140,6 @@ encode_func[dict] = encode_dict
encode_func[bool] = encode_bool
encode_func[str] = encode_string
encode_func[bytes] = encode_bytes
-if PY2:
- encode_func[long] = encode_int # noqa: F821
- encode_func[str] = encode_bytes
- encode_func[unicode] = encode_string # noqa: F821
def bencode(x):
diff --git a/deluge/common.py b/deluge/common.py
index a0e3fdd3b..ecf90a390 100644
--- a/deluge/common.py
+++ b/deluge/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007,2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -8,25 +7,25 @@
#
"""Common functions for various parts of Deluge to use."""
-from __future__ import division, print_function, unicode_literals
-
import base64
import binascii
import functools
import glob
-import locale
import logging
import numbers
import os
import platform
import re
+import socket
import subprocess
import sys
import tarfile
import time
from contextlib import closing
from datetime import datetime
-from io import BytesIO, open
+from io import BytesIO
+from urllib.parse import unquote_plus, urljoin
+from urllib.request import pathname2url
import pkg_resources
@@ -38,14 +37,6 @@ try:
except ImportError:
chardet = None
-try:
- from urllib.parse import unquote_plus, urljoin
- from urllib.request import pathname2url
-except ImportError:
- # PY2 fallback
- from urllib import pathname2url, unquote_plus # pylint: disable=ungrouped-imports
- from urlparse import urljoin # pylint: disable=ungrouped-imports
-
# Windows workaround for HTTPS requests requiring certificate authority bundle.
# see: https://twistedmatrix.com/trac/ticket/9209
if platform.system() in ('Windows', 'Microsoft'):
@@ -53,6 +44,11 @@ if platform.system() in ('Windows', 'Microsoft'):
os.environ['SSL_CERT_FILE'] = where()
+try:
+ import ifaddr
+except ImportError:
+ ifaddr = None
+
if platform.system() not in ('Windows', 'Microsoft', 'Darwin'):
# gi makes dbus available on Window but don't import it as unused.
@@ -84,7 +80,8 @@ JSON_FORMAT = {'indent': 4, 'sort_keys': True, 'ensure_ascii': False}
DBUS_FM_ID = 'org.freedesktop.FileManager1'
DBUS_FM_PATH = '/org/freedesktop/FileManager1'
-PY2 = sys.version_info.major == 2
+# Retained for plugin backward compatibility
+PY2 = False
def get_version():
@@ -111,10 +108,8 @@ def get_default_config_dir(filename=None):
def save_config_path(resource):
app_data_path = os.environ.get('APPDATA')
if not app_data_path:
- try:
- import winreg
- except ImportError:
- import _winreg as winreg # For Python 2.
+ import winreg
+
hkey = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders',
@@ -147,14 +142,14 @@ def get_default_download_dir():
try:
user_dirs_path = os.path.join(xdg_config_home, 'user-dirs.dirs')
- with open(user_dirs_path, 'r', encoding='utf8') as _file:
+ with open(user_dirs_path, encoding='utf8') as _file:
for line in _file:
if not line.startswith('#') and line.startswith('XDG_DOWNLOAD_DIR'):
download_dir = os.path.expandvars(
line.partition('=')[2].rstrip().strip('"')
)
break
- except IOError:
+ except OSError:
pass
if not download_dir:
@@ -178,8 +173,8 @@ def archive_files(arc_name, filepaths, message=None, rotate=10):
from deluge.configmanager import get_config_dir
- # Set archive compression to lzma with bz2 fallback.
- arc_comp = 'xz' if not PY2 else 'bz2'
+ # Set archive compression to lzma
+ arc_comp = 'xz'
archive_dir = os.path.join(get_config_dir(), 'archive')
timestamp = datetime.now().replace(microsecond=0).isoformat().replace(':', '-')
@@ -275,7 +270,7 @@ def get_os_version():
os_version = list(platform.mac_ver())
os_version[1] = '' # versioninfo always empty.
elif distro:
- os_version = distro.linux_distribution()
+ os_version = (distro.name(), distro.version(), distro.codename())
else:
os_version = (platform.release(),)
@@ -441,22 +436,22 @@ def fsize(fsize_b, precision=1, shortform=False):
"""
- if fsize_b >= 1024 ** 4:
+ if fsize_b >= 1024**4:
return '%.*f %s' % (
precision,
- fsize_b / 1024 ** 4,
+ fsize_b / 1024**4,
tib_txt_short if shortform else tib_txt,
)
- elif fsize_b >= 1024 ** 3:
+ elif fsize_b >= 1024**3:
return '%.*f %s' % (
precision,
- fsize_b / 1024 ** 3,
+ fsize_b / 1024**3,
gib_txt_short if shortform else gib_txt,
)
- elif fsize_b >= 1024 ** 2:
+ elif fsize_b >= 1024**2:
return '%.*f %s' % (
precision,
- fsize_b / 1024 ** 2,
+ fsize_b / 1024**2,
mib_txt_short if shortform else mib_txt,
)
elif fsize_b >= 1024:
@@ -508,28 +503,28 @@ def fspeed(bps, precision=1, shortform=False):
"""
- if bps < 1024 ** 2:
+ if bps < 1024**2:
return '%.*f %s' % (
precision,
bps / 1024,
_('K/s') if shortform else _('KiB/s'),
)
- elif bps < 1024 ** 3:
+ elif bps < 1024**3:
return '%.*f %s' % (
precision,
- bps / 1024 ** 2,
+ bps / 1024**2,
_('M/s') if shortform else _('MiB/s'),
)
- elif bps < 1024 ** 4:
+ elif bps < 1024**4:
return '%.*f %s' % (
precision,
- bps / 1024 ** 3,
+ bps / 1024**3,
_('G/s') if shortform else _('GiB/s'),
)
else:
return '%.*f %s' % (
precision,
- bps / 1024 ** 4,
+ bps / 1024**4,
_('T/s') if shortform else _('TiB/s'),
)
@@ -552,9 +547,9 @@ def fpeer(num_peers, total_peers):
"""
if total_peers > -1:
- return '{:d} ({:d})'.format(num_peers, total_peers)
+ return f'{num_peers:d} ({total_peers:d})'
else:
- return '{:d}'.format(num_peers)
+ return f'{num_peers:d}'
def ftime(secs):
@@ -580,17 +575,17 @@ def ftime(secs):
if secs <= 0:
time_str = ''
elif secs < 60:
- time_str = '{}s'.format(secs)
+ time_str = f'{secs}s'
elif secs < 3600:
- time_str = '{}m {}s'.format(secs // 60, secs % 60)
+ time_str = f'{secs // 60}m {secs % 60}s'
elif secs < 86400:
- time_str = '{}h {}m'.format(secs // 3600, secs // 60 % 60)
+ time_str = f'{secs // 3600}h {secs // 60 % 60}m'
elif secs < 604800:
- time_str = '{}d {}h'.format(secs // 86400, secs // 3600 % 24)
+ time_str = f'{secs // 86400}d {secs // 3600 % 24}h'
elif secs < 31449600:
- time_str = '{}w {}d'.format(secs // 604800, secs // 86400 % 7)
+ time_str = f'{secs // 604800}w {secs // 86400 % 7}d'
else:
- time_str = '{}y {}w'.format(secs // 31449600, secs // 604800 % 52)
+ time_str = f'{secs // 31449600}y {secs // 604800 % 52}w'
return time_str
@@ -644,17 +639,17 @@ def tokenize(text):
size_units = [
{'prefix': 'b', 'divider': 1, 'singular': 'byte', 'plural': 'bytes'},
- {'prefix': 'KiB', 'divider': 1024 ** 1},
- {'prefix': 'MiB', 'divider': 1024 ** 2},
- {'prefix': 'GiB', 'divider': 1024 ** 3},
- {'prefix': 'TiB', 'divider': 1024 ** 4},
- {'prefix': 'PiB', 'divider': 1024 ** 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},
+ {'prefix': 'KiB', 'divider': 1024**1},
+ {'prefix': 'MiB', 'divider': 1024**2},
+ {'prefix': 'GiB', 'divider': 1024**3},
+ {'prefix': 'TiB', 'divider': 1024**4},
+ {'prefix': 'PiB', 'divider': 1024**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},
]
@@ -712,6 +707,9 @@ def is_url(url):
True
"""
+ if not url:
+ return False
+
return url.partition('://')[0] in ('http', 'https', 'ftp', 'udp')
@@ -726,6 +724,9 @@ def is_infohash(infohash):
bool: True if valid infohash, False otherwise.
"""
+ if not infohash:
+ return False
+
return len(infohash) == 40 and infohash.isalnum()
@@ -733,6 +734,8 @@ MAGNET_SCHEME = 'magnet:?'
XT_BTIH_PARAM = 'xt=urn:btih:'
DN_PARAM = 'dn='
TR_PARAM = 'tr='
+TR_TIER_PARAM = 'tr.'
+TR_TIER_REGEX = re.compile(r'^tr.(\d+)=(\S+)')
def is_magnet(uri):
@@ -775,8 +778,6 @@ def get_magnet_info(uri):
"""
- tr0_param = 'tr.'
- tr0_param_regex = re.compile(r'^tr.(\d+)=(\S+)')
if not uri.startswith(MAGNET_SCHEME):
return {}
@@ -804,12 +805,14 @@ def get_magnet_info(uri):
tracker = unquote_plus(param[len(TR_PARAM) :])
trackers[tracker] = tier
tier += 1
- elif param.startswith(tr0_param):
- try:
- tier, tracker = re.match(tr0_param_regex, param).groups()
- trackers[tracker] = tier
- except AttributeError:
- pass
+ elif param.startswith(TR_TIER_PARAM):
+ tracker_match = re.match(TR_TIER_REGEX, param)
+ if not tracker_match:
+ continue
+
+ tier, tracker = tracker_match.groups()
+ tracker = unquote_plus(tracker)
+ trackers[tracker] = int(tier)
if info_hash:
if not name:
@@ -904,6 +907,29 @@ def free_space(path):
return disk_data.f_bavail * block_size
+def is_interface(interface):
+ """Check if interface is a valid IP or network adapter.
+
+ Args:
+ interface (str): The IP or interface name to test.
+
+ Returns:
+ bool: Whether interface is valid is not.
+
+ Examples:
+ Windows:
+ >>> is_interface('{7A30AE62-23ZA-3744-Z844-A5B042524871}')
+ >>> is_interface('127.0.0.1')
+ True
+ Linux:
+ >>> is_interface('lo')
+ >>> is_interface('127.0.0.1')
+ True
+
+ """
+ return is_ip(interface) or is_interface_name(interface)
+
+
def is_ip(ip):
"""A test to see if 'ip' is a valid IPv4 or IPv6 address.
@@ -939,15 +965,12 @@ def is_ipv4(ip):
"""
- import socket
-
try:
- if windows_check():
- return socket.inet_aton(ip)
- else:
- return socket.inet_pton(socket.AF_INET, ip)
- except socket.error:
+ socket.inet_pton(socket.AF_INET, ip)
+ except OSError:
return False
+ else:
+ return True
def is_ipv6(ip):
@@ -966,23 +989,51 @@ def is_ipv6(ip):
"""
try:
- import ipaddress
- except ImportError:
- import socket
-
- try:
- return socket.inet_pton(socket.AF_INET6, ip)
- except (socket.error, AttributeError):
- if windows_check():
- log.warning('Unable to verify IPv6 Address on Windows.')
- return True
+ socket.inet_pton(socket.AF_INET6, ip)
+ except OSError:
+ return False
else:
+ return True
+
+
+def is_interface_name(name):
+ """Returns True if an interface name exists.
+
+ Args:
+ name (str): The Interface to test. eg. eth0 linux. GUID on Windows.
+
+ Returns:
+ bool: Whether name is valid or not.
+
+ Examples:
+ >>> is_interface_name("eth0")
+ True
+ >>> is_interface_name("{7A30AE62-23ZA-3744-Z844-A5B042524871}")
+ True
+
+ """
+
+ if not windows_check():
try:
- return ipaddress.IPv6Address(decode_bytes(ip))
- except ipaddress.AddressValueError:
+ socket.if_nametoindex(name)
+ except OSError:
pass
+ else:
+ return True
+
+ if ifaddr:
+ try:
+ adapters = ifaddr.get_adapters()
+ except OSError:
+ return True
+ else:
+ return any([name == a.name for a in adapters])
+
+ if windows_check():
+ regex = '^{[0-9A-Z]{8}-([0-9A-Z]{4}-){3}[0-9A-Z]{12}}$'
+ return bool(re.search(regex, str(name)))
- return False
+ return True
def decode_bytes(byte_str, encoding='utf8'):
@@ -1060,7 +1111,7 @@ def utf8_encode_structure(data):
@functools.total_ordering
-class VersionSplit(object):
+class VersionSplit:
"""
Used for comparing version numbers.
@@ -1239,11 +1290,7 @@ def set_env_variable(name, value):
http://sourceforge.net/p/gramps/code/HEAD/tree/branches/maintenance/gramps32/src/TransUtils.py
"""
# Update Python's copy of the environment variables
- try:
- os.environ[name] = value
- except UnicodeEncodeError:
- # Python 2
- os.environ[name] = value.encode('utf8')
+ os.environ[name] = value
if windows_check():
from ctypes import cdll, windll
@@ -1262,56 +1309,13 @@ def set_env_variable(name, value):
)
# Update the copy maintained by msvcrt (used by gtk+ runtime)
- result = cdll.msvcrt._wputenv('%s=%s' % (name, value))
+ result = cdll.msvcrt._wputenv(f'{name}={value}')
if result != 0:
log.info("Failed to set Env Var '%s' (msvcrt._putenv)", name)
else:
log.debug("Set Env Var '%s' to '%s' (msvcrt._putenv)", name, value)
-def unicode_argv():
- """ Gets sys.argv as list of unicode objects on any platform."""
- if windows_check():
- # Versions 2.x of Python don't support Unicode in sys.argv on
- # Windows, with the underlying Windows API instead replacing multi-byte
- # characters with '?'.
- from ctypes import POINTER, byref, c_int, cdll, windll
- from ctypes.wintypes import LPCWSTR, LPWSTR
-
- get_cmd_linew = cdll.kernel32.GetCommandLineW
- get_cmd_linew.argtypes = []
- get_cmd_linew.restype = LPCWSTR
-
- cmdline_to_argvw = windll.shell32.CommandLineToArgvW
- cmdline_to_argvw.argtypes = [LPCWSTR, POINTER(c_int)]
- cmdline_to_argvw.restype = POINTER(LPWSTR)
-
- cmd = get_cmd_linew()
- argc = c_int(0)
- argv = cmdline_to_argvw(cmd, byref(argc))
- if argc.value > 0:
- # Remove Python executable and commands if present
- start = argc.value - len(sys.argv)
- return [argv[i] for i in range(start, argc.value)]
- else:
- # On other platforms, we have to find the likely encoding of the args and decode
- # First check if sys.stdout or stdin have encoding set
- encoding = getattr(sys.stdout, 'encoding') or getattr(sys.stdin, 'encoding')
- # If that fails, check what the locale is set to
- encoding = encoding or locale.getpreferredencoding()
- # As a last resort, just default to utf-8
- encoding = encoding or 'utf-8'
-
- arg_list = []
- for arg in sys.argv:
- try:
- arg_list.append(arg.decode(encoding))
- except AttributeError:
- arg_list.append(arg)
-
- return arg_list
-
-
def run_profiled(func, *args, **kwargs):
"""
Profile a function with cProfile
diff --git a/deluge/component.py b/deluge/component.py
index b7b7ab3e4..5646e8bd2 100644
--- a/deluge/component.py
+++ b/deluge/component.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,13 +6,10 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import traceback
from collections import defaultdict
-from six import string_types
from twisted.internet import reactor
from twisted.internet.defer import DeferredList, fail, maybeDeferred, succeed
from twisted.internet.task import LoopingCall, deferLater
@@ -27,13 +23,13 @@ class ComponentAlreadyRegistered(Exception):
class ComponentException(Exception):
def __init__(self, message, tb):
- super(ComponentException, self).__init__(message)
+ super().__init__(message)
self.message = message
self.tb = tb
def __str__(self):
- s = super(ComponentException, self).__str__()
- return '%s\n%s' % (s, ''.join(self.tb))
+ s = super().__str__()
+ return '{}\n{}'.format(s, ''.join(self.tb))
def __eq__(self, other):
if isinstance(other, self.__class__):
@@ -45,7 +41,7 @@ class ComponentException(Exception):
return not self.__eq__(other)
-class Component(object):
+class Component:
"""Component objects are singletons managed by the :class:`ComponentRegistry`.
When a new Component object is instantiated, it will be automatically
@@ -250,7 +246,7 @@ class Component(object):
pass
-class ComponentRegistry(object):
+class ComponentRegistry:
"""The ComponentRegistry holds a list of currently registered :class:`Component` objects.
It is used to manage the Components by starting, stopping, pausing and shutting them down.
@@ -325,7 +321,7 @@ class ComponentRegistry(object):
# Start all the components if names is empty
if not names:
names = list(self.components)
- elif isinstance(names, string_types):
+ elif isinstance(names, str):
names = [names]
def on_depends_started(result, name):
@@ -359,7 +355,7 @@ class ComponentRegistry(object):
"""
if not names:
names = list(self.components)
- elif isinstance(names, string_types):
+ elif isinstance(names, str):
names = [names]
def on_dependents_stopped(result, name):
@@ -399,7 +395,7 @@ class ComponentRegistry(object):
"""
if not names:
names = list(self.components)
- elif isinstance(names, string_types):
+ elif isinstance(names, str):
names = [names]
deferreds = []
@@ -425,7 +421,7 @@ class ComponentRegistry(object):
"""
if not names:
names = list(self.components)
- elif isinstance(names, string_types):
+ elif isinstance(names, str):
names = [names]
deferreds = []
diff --git a/deluge/config.py b/deluge/config.py
index 80b30faf5..c5cb3122b 100644
--- a/deluge/config.py
+++ b/deluge/config.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -39,39 +38,17 @@ this can only be done for the 'config file version' and not for the 'format'
version as this will be done internally.
"""
-from __future__ import unicode_literals
-
import json
import logging
import os
+import pickle
import shutil
from codecs import getwriter
-from io import open
from tempfile import NamedTemporaryFile
-import six.moves.cPickle as pickle # noqa: N813
-
from deluge.common import JSON_FORMAT, get_default_config_dir
log = logging.getLogger(__name__)
-callLater = None # noqa: N816 Necessary for the config tests
-
-
-def prop(func):
- """Function decorator for defining property attributes
-
- The decorated function is expected to return a dictionary
- containing one or more of the following pairs:
-
- fget - function for getting attribute value
- fset - function for setting attribute value
- fdel - function for deleting attribute
-
- This can be conveniently constructed by the locals() builtin
- function; see:
- http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
- """
- return property(doc=func.__doc__, **func())
def find_json_objects(text, decoder=json.JSONDecoder()):
@@ -105,7 +82,22 @@ def find_json_objects(text, decoder=json.JSONDecoder()):
return objects
-class Config(object):
+def cast_to_existing_type(value, old_value):
+ """Attempt to convert new value type to match old value type"""
+ types_match = isinstance(old_value, (type(None), type(value)))
+ if value is not None and not types_match:
+ old_type = type(old_value)
+ # Skip convert to bytes since requires knowledge of encoding and value should
+ # be unicode anyway.
+ if old_type is bytes:
+ return value
+
+ return old_type(value)
+
+ return value
+
+
+class Config:
"""This class is used to access/create/modify config files.
Args:
@@ -115,13 +107,23 @@ class Config(object):
file_version (int): The file format for the default config values when creating
a fresh config. This value should be increased whenever a new migration function is
setup to convert old config files. (default: 1)
+ log_mask_funcs (dict): A dict of key:function, used to mask sensitive
+ key values (e.g. passwords) when logging is enabled.
"""
- def __init__(self, filename, defaults=None, config_dir=None, file_version=1):
+ def __init__(
+ self,
+ filename,
+ defaults=None,
+ config_dir=None,
+ file_version=1,
+ log_mask_funcs=None,
+ ):
self.__config = {}
self.__set_functions = {}
self.__change_callbacks = []
+ self.__log_mask_funcs = log_mask_funcs if log_mask_funcs else {}
# These hold the version numbers and they will be set when loaded
self.__version = {'format': 1, 'file': file_version}
@@ -132,7 +134,7 @@ class Config(object):
if defaults:
for key, value in defaults.items():
- self.set_item(key, value)
+ self.set_item(key, value, default=True)
# Load the config from file in the config_dir
if config_dir:
@@ -142,6 +144,12 @@ class Config(object):
self.load()
+ def callLater(self, period, func, *args, **kwargs): # noqa: N802 ignore camelCase
+ """Wrapper around reactor.callLater for test purpose."""
+ from twisted.internet import reactor
+
+ return reactor.callLater(period, func, *args, **kwargs)
+
def __contains__(self, item):
return item in self.__config
@@ -150,7 +158,7 @@ class Config(object):
return self.set_item(key, value)
- def set_item(self, key, value):
+ def set_item(self, key, value, default=False):
"""Sets item 'key' to 'value' in the config dictionary.
Does not allow changing the item's type unless it is None.
@@ -162,6 +170,8 @@ class Config(object):
key (str): Item to change to change.
value (any): The value to change item to, must be same type as what is
currently in the config.
+ default (optional, bool): When setting a default value skip func or save
+ callbacks.
Raises:
ValueError: Raised when the type of value is not the same as what is
@@ -174,61 +184,54 @@ class Config(object):
5
"""
- if key not in self.__config:
- self.__config[key] = value
- log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(value))
- return
-
- if self.__config[key] == value:
- return
+ if isinstance(value, bytes):
+ value = value.decode()
- # Change the value type if it is not None and does not match.
- type_match = isinstance(self.__config[key], (type(None), type(value)))
- if value is not None and not type_match:
+ if key in self.__config:
try:
- oldtype = type(self.__config[key])
- # Don't convert to bytes as requires encoding and value will
- # be decoded anyway.
- if oldtype is not bytes:
- value = oldtype(value)
+ value = cast_to_existing_type(value, self.__config[key])
except ValueError:
log.warning('Value Type "%s" invalid for key: %s', type(value), key)
raise
+ else:
+ if self.__config[key] == value:
+ return
- if isinstance(value, bytes):
- value = value.decode('utf8')
-
- log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(value))
+ if log.isEnabledFor(logging.DEBUG):
+ if key in self.__log_mask_funcs:
+ value = self.__log_mask_funcs[key](value)
+ log.debug(
+ 'Setting key "%s" to: %s (of type: %s)',
+ key,
+ value,
+ type(value),
+ )
self.__config[key] = value
- global callLater
- if callLater is None:
- # Must import here and not at the top or it will throw ReactorAlreadyInstalledError
- from twisted.internet.reactor import ( # pylint: disable=redefined-outer-name
- callLater,
- )
+ # Skip save or func callbacks if setting default value for keys
+ if default:
+ return
+
# Run the set_function for this key if any
- try:
- for func in self.__set_functions[key]:
- callLater(0, func, key, value)
- except KeyError:
- pass
+ for func in self.__set_functions.get(key, []):
+ self.callLater(0, func, key, value)
+
try:
def do_change_callbacks(key, value):
for func in self.__change_callbacks:
func(key, value)
- callLater(0, do_change_callbacks, key, value)
+ self.callLater(0, do_change_callbacks, key, value)
except Exception:
pass
# We set the save_timer for 5 seconds if not already set
if not self._save_timer or not self._save_timer.active():
- self._save_timer = callLater(5, self.save)
+ self._save_timer = self.callLater(5, self.save)
def __getitem__(self, key):
- """See get_item """
+ """See get_item"""
return self.get_item(key)
def get_item(self, key):
@@ -301,16 +304,9 @@ class Config(object):
del self.__config[key]
- global callLater
- if callLater is None:
- # Must import here and not at the top or it will throw ReactorAlreadyInstalledError
- from twisted.internet.reactor import ( # pylint: disable=redefined-outer-name
- callLater,
- )
-
# We set the save_timer for 5 seconds if not already set
if not self._save_timer or not self._save_timer.active():
- self._save_timer = callLater(5, self.save)
+ self._save_timer = self.callLater(5, self.save)
def register_change_callback(self, callback):
"""Registers a callback function for any changed value.
@@ -356,7 +352,6 @@ class Config(object):
# Run the function now if apply_now is set
if apply_now:
function(key, self.__config[key])
- return
def apply_all(self):
"""Calls all set functions.
@@ -399,9 +394,9 @@ class Config(object):
filename = self.__config_file
try:
- with open(filename, 'r', encoding='utf8') as _file:
+ with open(filename, encoding='utf8') as _file:
data = _file.read()
- except IOError as ex:
+ except OSError as ex:
log.warning('Unable to open config file %s: %s', filename, ex)
return
@@ -431,12 +426,24 @@ class Config(object):
log.exception(ex)
log.warning('Unable to load config file: %s', filename)
+ if not log.isEnabledFor(logging.DEBUG):
+ return
+
+ config = self.__config
+ if self.__log_mask_funcs:
+ config = {
+ key: self.__log_mask_funcs[key](config[key])
+ if key in self.__log_mask_funcs
+ else config[key]
+ for key in config
+ }
+
log.debug(
'Config %s version: %s.%s loaded: %s',
filename,
self.__version['format'],
self.__version['file'],
- self.__config,
+ config,
)
def save(self, filename=None):
@@ -454,7 +461,7 @@ class Config(object):
# Check to see if the current config differs from the one on disk
# We will only write a new config file if there is a difference
try:
- with open(filename, 'r', encoding='utf8') as _file:
+ with open(filename, encoding='utf8') as _file:
data = _file.read()
objects = find_json_objects(data)
start, end = objects[0]
@@ -466,7 +473,7 @@ class Config(object):
if self._save_timer and self._save_timer.active():
self._save_timer.cancel()
return True
- except (IOError, IndexError) as ex:
+ except (OSError, IndexError) as ex:
log.warning('Unable to open config file: %s because: %s', filename, ex)
# Save the new config and make sure it's written to disk
@@ -480,7 +487,7 @@ class Config(object):
json.dump(self.__config, getwriter('utf8')(_file), **JSON_FORMAT)
_file.flush()
os.fsync(_file.fileno())
- except IOError as ex:
+ except OSError as ex:
log.error('Error writing new config file: %s', ex)
return False
@@ -491,7 +498,7 @@ class Config(object):
try:
log.debug('Backing up old config file to %s.bak', filename)
shutil.move(filename, filename + '.bak')
- except IOError as ex:
+ except OSError as ex:
log.warning('Unable to backup old config: %s', ex)
# The new config file has been written successfully, so let's move it over
@@ -499,7 +506,7 @@ class Config(object):
try:
log.debug('Moving new config file %s to %s', filename_tmp, filename)
shutil.move(filename_tmp, filename)
- except IOError as ex:
+ except OSError as ex:
log.error('Error moving new config file: %s', ex)
return False
else:
@@ -551,14 +558,11 @@ class Config(object):
def config_file(self):
return self.__config_file
- @prop
- def config(): # pylint: disable=no-method-argument
+ @property
+ def config(self):
"""The config dictionary"""
+ return self.__config
- def fget(self):
- return self.__config
-
- def fdel(self):
- return self.save()
-
- return locals()
+ @config.deleter
+ def config(self):
+ return self.save()
diff --git a/deluge/configmanager.py b/deluge/configmanager.py
index bbb0389a5..6e965b863 100644
--- a/deluge/configmanager.py
+++ b/deluge/configmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
@@ -19,7 +16,7 @@ from deluge.config import Config
log = logging.getLogger(__name__)
-class _ConfigManager(object):
+class _ConfigManager:
def __init__(self):
log.debug('ConfigManager started..')
self.config_files = {}
diff --git a/deluge/conftest.py b/deluge/conftest.py
new file mode 100644
index 000000000..55c50a42d
--- /dev/null
+++ b/deluge/conftest.py
@@ -0,0 +1,192 @@
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import tempfile
+import warnings
+from unittest.mock import Mock, patch
+
+import pytest
+import pytest_twisted
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred, maybeDeferred
+from twisted.internet.error import CannotListenError
+from twisted.python.failure import Failure
+
+import deluge.component as _component
+import deluge.configmanager
+from deluge.common import get_localhost_auth
+from deluge.tests import common
+from deluge.ui.client import client as _client
+
+DEFAULT_LISTEN_PORT = 58900
+
+
+@pytest.fixture
+def listen_port(request):
+ if request and 'daemon' in request.fixturenames:
+ try:
+ return request.getfixturevalue('daemon').listen_port
+ except Exception:
+ pass
+ return DEFAULT_LISTEN_PORT
+
+
+@pytest.fixture
+def mock_callback():
+ """Returns a `Mock` object which can be registered as a callback to test against.
+
+ If callback was not called within `timeout` seconds, it will raise a TimeoutError.
+ The returned Mock instance will have a `deferred` attribute which will complete when the callback has been called.
+ """
+
+ def reset():
+ if mock.called:
+ original_reset_mock()
+ deferred = Deferred()
+ deferred.addTimeout(0.5, reactor)
+ mock.side_effect = lambda *args, **kw: deferred.callback((args, kw))
+ mock.deferred = deferred
+
+ mock = Mock()
+ original_reset_mock = mock.reset_mock
+ mock.reset_mock = reset
+ mock.reset_mock()
+ return mock
+
+
+@pytest.fixture
+def config_dir(tmp_path):
+ deluge.configmanager.set_config_dir(tmp_path)
+ yield tmp_path
+
+
+@pytest_twisted.async_yield_fixture()
+async def client(request, config_dir, monkeypatch, listen_port):
+ # monkeypatch.setattr(
+ # _client, 'connect', functools.partial(_client.connect, port=listen_port)
+ # )
+ try:
+ username, password = get_localhost_auth()
+ except Exception:
+ username, password = '', ''
+ await _client.connect(
+ 'localhost',
+ port=listen_port,
+ username=username,
+ password=password,
+ )
+ yield _client
+ if _client.connected():
+ await _client.disconnect()
+
+
+@pytest_twisted.async_yield_fixture
+async def daemon(request, config_dir):
+ listen_port = DEFAULT_LISTEN_PORT
+ logfile = f'daemon_{request.node.name}.log'
+ if hasattr(request.cls, 'daemon_custom_script'):
+ custom_script = request.cls.daemon_custom_script
+ else:
+ custom_script = ''
+
+ for dummy in range(10):
+ try:
+ d, daemon = common.start_core(
+ listen_port=listen_port,
+ logfile=logfile,
+ timeout=5,
+ timeout_msg='Timeout!',
+ custom_script=custom_script,
+ print_stdout=True,
+ print_stderr=True,
+ config_directory=config_dir,
+ )
+ await d
+ except CannotListenError as ex:
+ exception_error = ex
+ listen_port += 1
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ else:
+ break
+ else:
+ raise exception_error
+ daemon.listen_port = listen_port
+ yield daemon
+ await daemon.kill()
+
+
+@pytest.fixture(autouse=True)
+def common_fixture(config_dir, request, monkeypatch, listen_port):
+ """Adds some instance attributes to test classes for backwards compatibility with old testing."""
+
+ def fail(self, reason):
+ if isinstance(reason, Failure):
+ reason = reason.value
+ return pytest.fail(str(reason))
+
+ if request.instance:
+ request.instance.patch = monkeypatch.setattr
+ request.instance.config_dir = config_dir
+ request.instance.listen_port = listen_port
+ request.instance.id = lambda: request.node.name
+ request.cls.fail = fail
+
+
+@pytest_twisted.async_yield_fixture(scope='function')
+async def component(request):
+ """Verify component registry is clean, and clean up after test."""
+ if len(_component._ComponentRegistry.components) != 0:
+ warnings.warn(
+ 'The component._ComponentRegistry.components is not empty on test setup.\n'
+ 'This is probably caused by another test that did not clean up after finishing!: %s'
+ % _component._ComponentRegistry.components
+ )
+
+ yield _component
+
+ await _component.shutdown()
+ _component._ComponentRegistry.components.clear()
+ _component._ComponentRegistry.dependents.clear()
+
+
+@pytest_twisted.async_yield_fixture(scope='function')
+async def base_fixture(common_fixture, component, request):
+ """This fixture is autoused on all tests that subclass BaseTestCase"""
+ self = request.instance
+
+ if hasattr(self, 'set_up'):
+ try:
+ await maybeDeferred(self.set_up)
+ except Exception as exc:
+ warnings.warn('Error caught in test setup!\n%s' % exc)
+ pytest.fail('Error caught in test setup!\n%s' % exc)
+
+ yield
+
+ if hasattr(self, 'tear_down'):
+ try:
+ await maybeDeferred(self.tear_down)
+ except Exception as exc:
+ pytest.fail('Error caught in test teardown!\n%s' % exc)
+
+
+@pytest.mark.usefixtures('base_fixture')
+class BaseTestCase:
+ """This is the base class that should be used for all test classes
+ that create classes that inherit from deluge.component.Component. It
+ ensures that the component registry has been cleaned up when tests
+ have finished.
+
+ """
+
+
+@pytest.fixture
+def mock_mkstemp(tmp_path):
+ """Return known tempfile location to verify file deleted"""
+ tmp_file = tempfile.mkstemp(dir=tmp_path)
+ with patch('tempfile.mkstemp', return_value=tmp_file):
+ yield tmp_file
diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py
index 2fe42224d..9a1ded52e 100644
--- a/deluge/core/alertmanager.py
+++ b/deluge/core/alertmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -15,10 +14,8 @@ This should typically only be used by the Core. Plugins should utilize the
`:mod:EventManager` for similar functionality.
"""
-from __future__ import unicode_literals
-
import logging
-import types
+from types import SimpleNamespace
from twisted.internet import reactor
@@ -28,14 +25,6 @@ from deluge.common import decode_bytes
log = logging.getLogger(__name__)
-try:
- SimpleNamespace = types.SimpleNamespace # Python 3.3+
-except AttributeError:
-
- class SimpleNamespace(object): # Python 2.7
- def __init__(self, **attr):
- self.__dict__.update(attr)
-
class AlertManager(component.Component):
"""AlertManager fetches and processes libtorrent alerts"""
@@ -57,6 +46,7 @@ class AlertManager(component.Component):
| lt.alert.category_t.status_notification
| lt.alert.category_t.ip_block_notification
| lt.alert.category_t.performance_warning
+ | lt.alert.category_t.file_progress_notification
)
self.session.apply_settings({'alert_mask': alert_mask})
diff --git a/deluge/core/authmanager.py b/deluge/core/authmanager.py
index 713373e99..3ff8a3ad9 100644
--- a/deluge/core/authmanager.py
+++ b/deluge/core/authmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@@ -8,12 +7,9 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
import shutil
-from io import open
import deluge.component as component
import deluge.configmanager as configmanager
@@ -32,14 +28,14 @@ log = logging.getLogger(__name__)
AUTH_LEVELS_MAPPING = {
'NONE': AUTH_LEVEL_NONE,
'READONLY': AUTH_LEVEL_READONLY,
- 'DEFAULT': AUTH_LEVEL_NORMAL,
- 'NORMAL': AUTH_LEVEL_DEFAULT,
+ 'DEFAULT': AUTH_LEVEL_DEFAULT,
+ 'NORMAL': AUTH_LEVEL_NORMAL,
'ADMIN': AUTH_LEVEL_ADMIN,
}
AUTH_LEVELS_MAPPING_REVERSE = {v: k for k, v in AUTH_LEVELS_MAPPING.items()}
-class Account(object):
+class Account:
__slots__ = ('username', 'password', 'authlevel')
def __init__(self, username, password, authlevel):
@@ -56,10 +52,10 @@ class Account(object):
}
def __repr__(self):
- return '<Account username="%(username)s" authlevel=%(authlevel)s>' % {
- 'username': self.username,
- 'authlevel': self.authlevel,
- }
+ return '<Account username="{username}" authlevel={authlevel}>'.format(
+ username=self.username,
+ authlevel=self.authlevel,
+ )
class AuthManager(component.Component):
@@ -184,7 +180,7 @@ class AuthManager(component.Component):
if os.path.isfile(filepath):
log.debug('Creating backup of %s at: %s', filename, filepath_bak)
shutil.copy2(filepath, filepath_bak)
- except IOError as ex:
+ except OSError as ex:
log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex)
else:
log.info('Saving the %s at: %s', filename, filepath)
@@ -198,7 +194,7 @@ class AuthManager(component.Component):
_file.flush()
os.fsync(_file.fileno())
shutil.move(filepath_tmp, filepath)
- except IOError as ex:
+ except OSError as ex:
log.error('Unable to save %s: %s', filename, ex)
if os.path.isfile(filepath_bak):
log.info('Restoring backup of %s from: %s', filename, filepath_bak)
@@ -227,9 +223,9 @@ class AuthManager(component.Component):
for _filepath in (auth_file, auth_file_bak):
log.info('Opening %s for load: %s', filename, _filepath)
try:
- with open(_filepath, 'r', encoding='utf8') as _file:
+ with open(_filepath, encoding='utf8') as _file:
file_data = _file.readlines()
- except IOError as ex:
+ except OSError as ex:
log.warning('Unable to load %s: %s', _filepath, ex)
file_data = []
else:
diff --git a/deluge/core/core.py b/deluge/core/core.py
index 881f0065d..35cf0194f 100644
--- a/deluge/core/core.py
+++ b/deluge/core/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import glob
import logging
import os
@@ -17,8 +14,9 @@ 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 six import string_types
from twisted.internet import defer, reactor, task
from twisted.web.client import Agent, readBody
@@ -41,7 +39,7 @@ from deluge.core.pluginmanager import PluginManager
from deluge.core.preferencesmanager import PreferencesManager
from deluge.core.rpcserver import export
from deluge.core.torrentmanager import TorrentManager
-from deluge.decorators import deprecated
+from deluge.decorators import deprecated, maybe_coroutine
from deluge.error import (
AddTorrentError,
DelugeError,
@@ -56,12 +54,6 @@ from deluge.event import (
)
from deluge.httpdownloader import download_file
-try:
- from urllib.request import URLError, urlopen
-except ImportError:
- # PY2 fallback
- from urllib2 import URLError, urlopen
-
log = logging.getLogger(__name__)
DEPR_SESSION_STATUS_KEYS = {
@@ -120,7 +112,7 @@ class Core(component.Component):
component.Component.__init__(self, 'Core')
# Start the libtorrent session.
- user_agent = 'Deluge/{} libtorrent/{}'.format(DELUGE_VER, LT_VERSION)
+ user_agent = f'Deluge/{DELUGE_VER} libtorrent/{LT_VERSION}'
peer_id = self._create_peer_id(DELUGE_VER)
log.debug('Starting session (peer_id: %s, user_agent: %s)', peer_id, user_agent)
settings_pack = {
@@ -173,19 +165,25 @@ class Core(component.Component):
# store the one in the config so we can restore it on shutdown
self._old_listen_interface = None
if listen_interface:
- if deluge.common.is_ip(listen_interface):
+ if deluge.common.is_interface(listen_interface):
self._old_listen_interface = self.config['listen_interface']
self.config['listen_interface'] = listen_interface
else:
log.error(
- 'Invalid listen interface (must be IP Address): %s',
+ 'Invalid listen interface (must be IP Address or Interface Name): %s',
listen_interface,
)
self._old_outgoing_interface = None
if outgoing_interface:
- self._old_outgoing_interface = self.config['outgoing_interface']
- self.config['outgoing_interface'] = outgoing_interface
+ if deluge.common.is_interface(outgoing_interface):
+ self._old_outgoing_interface = self.config['outgoing_interface']
+ self.config['outgoing_interface'] = outgoing_interface
+ else:
+ log.error(
+ 'Invalid outgoing interface (must be IP Address or Interface Name): %s',
+ outgoing_interface,
+ )
# New release check information
self.__new_release = None
@@ -243,13 +241,12 @@ class Core(component.Component):
"""Apply libtorrent session settings.
Args:
- settings (dict): A dict of lt session settings to apply.
-
+ settings: A dict of lt session settings to apply.
"""
self.session.apply_settings(settings)
@staticmethod
- def _create_peer_id(version):
+ def _create_peer_id(version: str) -> str:
"""Create a peer_id fingerprint.
This creates the peer_id and modifies the release char to identify
@@ -264,11 +261,10 @@ class Core(component.Component):
``--DE201b--`` (beta pre-release of v2.0.1)
Args:
- version (str): The version string in PEP440 dotted notation.
+ version: The version string in PEP440 dotted notation.
Returns:
- str: The formatted peer_id with Deluge prefix e.g. '--DE200s--'
-
+ The formatted peer_id with Deluge prefix e.g. '--DE200s--'
"""
split = deluge.common.VersionSplit(version)
# Fill list with zeros to length of 4 and use lt to create fingerprint.
@@ -301,7 +297,7 @@ class Core(component.Component):
if os.path.isfile(filepath):
log.debug('Creating backup of %s at: %s', filename, filepath_bak)
shutil.copy2(filepath, filepath_bak)
- except IOError as ex:
+ except OSError as ex:
log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex)
else:
log.info('Saving the %s at: %s', filename, filepath)
@@ -311,18 +307,17 @@ class Core(component.Component):
_file.flush()
os.fsync(_file.fileno())
shutil.move(filepath_tmp, filepath)
- except (IOError, EOFError) as ex:
+ except (OSError, EOFError) as ex:
log.error('Unable to save %s: %s', filename, ex)
if os.path.isfile(filepath_bak):
log.info('Restoring backup of %s from: %s', filename, filepath_bak)
shutil.move(filepath_bak, filepath)
- def _load_session_state(self):
+ def _load_session_state(self) -> dict:
"""Loads the libtorrent session state
Returns:
- dict: A libtorrent sesion state, empty dict if unable to load it.
-
+ A libtorrent sesion state, empty dict if unable to load it.
"""
filename = 'session.state'
filepath = get_config_dir(filename)
@@ -333,7 +328,7 @@ class Core(component.Component):
try:
with open(_filepath, 'rb') as _file:
state = lt.bdecode(_file.read())
- except (IOError, EOFError, RuntimeError) as ex:
+ except (OSError, EOFError, RuntimeError) as ex:
log.warning('Unable to load %s: %s', _filepath, ex)
else:
log.info('Successfully loaded %s: %s', filename, _filepath)
@@ -404,18 +399,19 @@ class Core(component.Component):
# Exported Methods
@export
- def add_torrent_file_async(self, filename, filedump, options, save_state=True):
+ def add_torrent_file_async(
+ self, filename: str, filedump: str, options: dict, save_state: bool = True
+ ) -> 'defer.Deferred[Optional[str]]':
"""Adds a torrent file to the session asynchronously.
Args:
- filename (str): The filename of the torrent.
- filedump (str): A base64 encoded string of torrent file contents.
- options (dict): The options to apply to the torrent upon adding.
- save_state (bool): If the state should be saved after adding the file.
+ filename: The filename of the torrent.
+ filedump: A base64 encoded string of torrent file contents.
+ options: The options to apply to the torrent upon adding.
+ save_state: If the state should be saved after adding the file.
Returns:
- Deferred: The torrent ID or None.
-
+ The torrent ID or None.
"""
try:
filedump = b64decode(filedump)
@@ -436,42 +432,39 @@ class Core(component.Component):
return d
@export
- def prefetch_magnet_metadata(self, magnet, timeout=30):
+ @maybe_coroutine
+ async def prefetch_magnet_metadata(
+ self, magnet: str, timeout: int = 30
+ ) -> Tuple[str, bytes]:
"""Download magnet metadata without adding to Deluge session.
Used by UIs to get magnet files for selection before adding to session.
+ The metadata is bencoded and for transfer base64 encoded.
+
Args:
- magnet (str): The magnet URI.
- timeout (int): Number of seconds to wait before canceling request.
+ magnet: The magnet URI.
+ timeout: Number of seconds to wait before canceling request.
Returns:
- Deferred: A tuple of (torrent_id (str), metadata (dict)) for the magnet.
+ A tuple of (torrent_id, metadata) for the magnet.
"""
-
- def on_metadata(result, result_d):
- """Return result of torrent_id and metadata"""
- result_d.callback(result)
- return result
-
- d = self.torrentmanager.prefetch_metadata(magnet, timeout)
- # Use a separate callback chain to handle existing prefetching magnet.
- result_d = defer.Deferred()
- d.addBoth(on_metadata, result_d)
- return result_d
+ return await self.torrentmanager.prefetch_metadata(magnet, timeout)
@export
- def add_torrent_file(self, filename, filedump, options):
+ def add_torrent_file(
+ self, filename: str, filedump: Union[str, bytes], options: dict
+ ) -> Optional[str]:
"""Adds a torrent file to the session.
Args:
- filename (str): The filename of the torrent.
- filedump (str): A base64 encoded string of the torrent file contents.
- options (dict): The options to apply to the torrent upon adding.
+ filename: The filename of the torrent.
+ filedump: A base64 encoded string of the torrent file contents.
+ options: The options to apply to the torrent upon adding.
Returns:
- str: The torrent_id or None.
+ The torrent_id or None.
"""
try:
filedump = b64decode(filedump)
@@ -487,25 +480,26 @@ class Core(component.Component):
raise
@export
- def add_torrent_files(self, torrent_files):
+ def add_torrent_files(
+ self, torrent_files: List[Tuple[str, Union[str, bytes], dict]]
+ ) -> 'defer.Deferred[List[AddTorrentError]]':
"""Adds multiple torrent files to the session asynchronously.
Args:
- torrent_files (list of tuples): Torrent files as tuple of
- ``(filename, filedump, options)``.
+ torrent_files: Torrent files as tuple of
+ ``(filename, filedump, options)``.
Returns:
- Deferred
-
+ A list of errors (if there were any)
"""
- @defer.inlineCallbacks
- def add_torrents():
+ @maybe_coroutine
+ async def add_torrents():
errors = []
last_index = len(torrent_files) - 1
for idx, torrent in enumerate(torrent_files):
try:
- yield self.add_torrent_file_async(
+ await self.add_torrent_file_async(
torrent[0], torrent[1], torrent[2], save_state=idx == last_index
)
except AddTorrentError as ex:
@@ -516,93 +510,89 @@ class Core(component.Component):
return task.deferLater(reactor, 0, add_torrents)
@export
- def add_torrent_url(self, url, options, headers=None):
- """
- Adds a torrent from a URL. Deluge will attempt to fetch the torrent
+ @maybe_coroutine
+ async def add_torrent_url(
+ self, url: str, options: dict, headers: dict = None
+ ) -> 'defer.Deferred[Optional[str]]':
+ """Adds a torrent from a URL. Deluge will attempt to fetch the torrent
from the URL prior to adding it to the session.
- :param url: the URL pointing to the torrent file
- :type url: string
- :param options: the options to apply to the torrent on add
- :type options: dict
- :param headers: any optional headers to send
- :type headers: dict
+ Args:
+ url: the URL pointing to the torrent file
+ options: the options to apply to the torrent on add
+ headers: any optional headers to send
- :returns: a Deferred which returns the torrent_id as a str or None
+ Returns:
+ a Deferred which returns the torrent_id as a str or None
"""
log.info('Attempting to add URL %s', url)
- def on_download_success(filename):
- # We got the file, so add it to the session
+ tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent')
+ try:
+ filename = await download_file(
+ url, tmp_file, headers=headers, force_filename=True
+ )
+ except Exception:
+ log.error('Failed to add torrent from URL %s', url)
+ raise
+ else:
with open(filename, 'rb') as _file:
data = _file.read()
+ return self.add_torrent_file(filename, b64encode(data), options)
+ finally:
try:
- os.remove(filename)
+ os.close(tmp_fd)
+ os.remove(tmp_file)
except OSError as ex:
- log.warning('Could not remove temp file: %s', ex)
- return self.add_torrent_file(filename, b64encode(data), options)
-
- def on_download_fail(failure):
- # Log the error and pass the failure onto the client
- log.error('Failed to add torrent from URL %s', url)
- return failure
-
- tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent')
- os.close(tmp_fd)
- d = download_file(url, tmp_file, headers=headers, force_filename=True)
- d.addCallbacks(on_download_success, on_download_fail)
- return d
+ log.warning(f'Unable to delete temp file {tmp_file}: , {ex}')
@export
- def add_torrent_magnet(self, uri, options):
- """
- Adds a torrent from a magnet link.
+ def add_torrent_magnet(self, uri: str, options: dict) -> str:
+ """Adds a torrent from a magnet link.
- :param uri: the magnet link
- :type uri: string
- :param options: the options to apply to the torrent on add
- :type options: dict
-
- :returns: the torrent_id
- :rtype: string
+ Args:
+ uri: the magnet link
+ options: the options to apply to the torrent on add
+ Returns:
+ the torrent_id
"""
log.debug('Attempting to add by magnet URI: %s', uri)
return self.torrentmanager.add(magnet=uri, options=options)
@export
- def remove_torrent(self, torrent_id, remove_data):
+ def remove_torrent(self, torrent_id: str, remove_data: bool) -> bool:
"""Removes a single torrent from the session.
Args:
- torrent_id (str): The torrent ID to remove.
- remove_data (bool): If True, also remove the downloaded data.
+ torrent_id: The torrent ID to remove.
+ remove_data: If True, also remove the downloaded data.
Returns:
- bool: True if removed successfully.
+ True if removed successfully.
Raises:
InvalidTorrentError: If the torrent ID does not exist in the session.
-
"""
log.debug('Removing torrent %s from the core.', torrent_id)
return self.torrentmanager.remove(torrent_id, remove_data)
@export
- def remove_torrents(self, torrent_ids, remove_data):
+ def remove_torrents(
+ self, torrent_ids: List[str], remove_data: bool
+ ) -> 'defer.Deferred[List[Tuple[str, str]]]':
"""Remove multiple torrents from the session.
Args:
- torrent_ids (list): The torrent IDs to remove.
- remove_data (bool): If True, also remove the downloaded data.
+ torrent_ids: The torrent IDs to remove.
+ remove_data: If True, also remove the downloaded data.
Returns:
- list: An empty list if no errors occurred otherwise the list contains
- tuples of strings, a torrent ID and an error message. For example:
-
- [('<torrent_id>', 'Error removing torrent')]
+ An empty list if no errors occurred otherwise the list contains
+ tuples of strings, a torrent ID and an error message. For example:
+ [('<torrent_id>', 'Error removing torrent')]
"""
log.info('Removing %d torrents from core.', len(torrent_ids))
@@ -626,17 +616,17 @@ class Core(component.Component):
return task.deferLater(reactor, 0, do_remove_torrents)
@export
- def get_session_status(self, keys):
+ def get_session_status(self, keys: List[str]) -> Dict[str, Union[int, float]]:
"""Gets the session status values for 'keys', these keys are taking
from libtorrent's session status.
See: http://www.rasterbar.com/products/libtorrent/manual.html#status
- :param keys: the keys for which we want values
- :type keys: list
- :returns: a dictionary of {key: value, ...}
- :rtype: dict
+ Args:
+ keys: the keys for which we want values
+ Returns:
+ a dictionary of {key: value, ...}
"""
if not keys:
return self.session_status
@@ -657,22 +647,22 @@ class Core(component.Component):
return status
@export
- def force_reannounce(self, torrent_ids):
+ def force_reannounce(self, torrent_ids: List[str]) -> None:
log.debug('Forcing reannouncment to: %s', torrent_ids)
for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].force_reannounce()
@export
- def pause_torrent(self, torrent_id):
+ def pause_torrent(self, torrent_id: str) -> None:
"""Pauses a torrent"""
log.debug('Pausing: %s', torrent_id)
- if not isinstance(torrent_id, string_types):
+ if not isinstance(torrent_id, str):
self.pause_torrents(torrent_id)
else:
self.torrentmanager[torrent_id].pause()
@export
- def pause_torrents(self, torrent_ids=None):
+ def pause_torrents(self, torrent_ids: List[str] = None) -> None:
"""Pauses a list of torrents"""
if not torrent_ids:
torrent_ids = self.torrentmanager.get_torrent_list()
@@ -680,27 +670,27 @@ class Core(component.Component):
self.pause_torrent(torrent_id)
@export
- def connect_peer(self, torrent_id, ip, port):
+ def connect_peer(self, torrent_id: str, ip: str, port: int):
log.debug('adding peer %s to %s', ip, torrent_id)
if not self.torrentmanager[torrent_id].connect_peer(ip, port):
log.warning('Error adding peer %s:%s to %s', ip, port, torrent_id)
@export
- def move_storage(self, torrent_ids, dest):
+ def move_storage(self, torrent_ids: List[str], dest: str):
log.debug('Moving storage %s to %s', torrent_ids, dest)
for torrent_id in torrent_ids:
if not self.torrentmanager[torrent_id].move_storage(dest):
log.warning('Error moving torrent %s to %s', torrent_id, dest)
@export
- def pause_session(self):
+ def pause_session(self) -> None:
"""Pause the entire session"""
if not self.session.is_paused():
self.session.pause()
component.get('EventManager').emit(SessionPausedEvent())
@export
- def resume_session(self):
+ def resume_session(self) -> None:
"""Resume the entire session"""
if self.session.is_paused():
self.session.resume()
@@ -709,21 +699,21 @@ class Core(component.Component):
component.get('EventManager').emit(SessionResumedEvent())
@export
- def is_session_paused(self):
+ def is_session_paused(self) -> bool:
"""Returns the activity of the session"""
return self.session.is_paused()
@export
- def resume_torrent(self, torrent_id):
+ def resume_torrent(self, torrent_id: str) -> None:
"""Resumes a torrent"""
log.debug('Resuming: %s', torrent_id)
- if not isinstance(torrent_id, string_types):
+ if not isinstance(torrent_id, str):
self.resume_torrents(torrent_id)
else:
self.torrentmanager[torrent_id].resume()
@export
- def resume_torrents(self, torrent_ids=None):
+ def resume_torrents(self, torrent_ids: List[str] = None) -> None:
"""Resumes a list of torrents"""
if not torrent_ids:
torrent_ids = self.torrentmanager.get_torrent_list()
@@ -756,7 +746,9 @@ class Core(component.Component):
return status
@export
- def get_torrent_status(self, torrent_id, keys, diff=False):
+ def get_torrent_status(
+ self, torrent_id: str, keys: List[str], diff: bool = False
+ ) -> dict:
torrent_keys, plugin_keys = self.torrentmanager.separate_keys(
keys, [torrent_id]
)
@@ -770,57 +762,54 @@ class Core(component.Component):
)
@export
- def get_torrents_status(self, filter_dict, keys, diff=False):
- """
- returns all torrents , optionally filtered by filter_dict.
- """
+ @maybe_coroutine
+ async def get_torrents_status(
+ self, filter_dict: dict, keys: List[str], diff: bool = False
+ ) -> dict:
+ """returns all torrents , optionally filtered by filter_dict."""
+ all_keys = not keys
torrent_ids = self.filtermanager.filter_torrent_ids(filter_dict)
- d = self.torrentmanager.torrents_status_update(torrent_ids, keys, diff=diff)
-
- def add_plugin_fields(args):
- status_dict, plugin_keys = args
- # Ask the plugin manager to fill in the plugin keys
- if len(plugin_keys) > 0:
- for key in status_dict:
- status_dict[key].update(
- self.pluginmanager.get_status(key, plugin_keys)
- )
- return status_dict
-
- d.addCallback(add_plugin_fields)
- return d
+ status_dict, plugin_keys = await self.torrentmanager.torrents_status_update(
+ torrent_ids, keys, diff=diff
+ )
+ # Ask the plugin manager to fill in the plugin keys
+ if len(plugin_keys) > 0 or all_keys:
+ for key in status_dict:
+ status_dict[key].update(self.pluginmanager.get_status(key, plugin_keys))
+ return status_dict
@export
- def get_filter_tree(self, show_zero_hits=True, hide_cat=None):
- """
- returns {field: [(value,count)] }
+ def get_filter_tree(
+ self, show_zero_hits: bool = True, hide_cat: List[str] = None
+ ) -> Dict:
+ """returns {field: [(value,count)] }
for use in sidebar(s)
"""
return self.filtermanager.get_filter_tree(show_zero_hits, hide_cat)
@export
- def get_session_state(self):
+ def get_session_state(self) -> List[str]:
"""Returns a list of torrent_ids in the session."""
# Get the torrent list from the TorrentManager
return self.torrentmanager.get_torrent_list()
@export
- def get_config(self):
+ def get_config(self) -> dict:
"""Get all the preferences as a dictionary"""
return self.config.config
@export
- def get_config_value(self, key):
+ def get_config_value(self, key: str) -> Any:
"""Get the config value for key"""
return self.config.get(key)
@export
- def get_config_values(self, keys):
+ def get_config_values(self, keys: List[str]) -> Dict[str, Any]:
"""Get the config values for the entered keys"""
return {key: self.config.get(key) for key in keys}
@export
- def set_config(self, config):
+ def set_config(self, config: Dict[str, Any]):
"""Set the config with values from dictionary"""
# Load all the values into the configuration
for key in config:
@@ -829,21 +818,20 @@ class Core(component.Component):
self.config[key] = config[key]
@export
- def get_listen_port(self):
+ def get_listen_port(self) -> int:
"""Returns the active listen port"""
return self.session.listen_port()
@export
- def get_proxy(self):
+ def get_proxy(self) -> Dict[str, Any]:
"""Returns the proxy settings
Returns:
- dict: Contains proxy settings.
+ Proxy settings.
Notes:
Proxy type names:
0: None, 1: Socks4, 2: Socks5, 3: Socks5 w Auth, 4: HTTP, 5: HTTP w Auth, 6: I2P
-
"""
settings = self.session.get_settings()
@@ -866,54 +854,58 @@ class Core(component.Component):
return proxy_dict
@export
- def get_available_plugins(self):
+ def get_available_plugins(self) -> List[str]:
"""Returns a list of plugins available in the core"""
return self.pluginmanager.get_available_plugins()
@export
- def get_enabled_plugins(self):
+ def get_enabled_plugins(self) -> List[str]:
"""Returns a list of enabled plugins in the core"""
return self.pluginmanager.get_enabled_plugins()
@export
- def enable_plugin(self, plugin):
+ def enable_plugin(self, plugin: str) -> 'defer.Deferred[bool]':
return self.pluginmanager.enable_plugin(plugin)
@export
- def disable_plugin(self, plugin):
+ def disable_plugin(self, plugin: str) -> 'defer.Deferred[bool]':
return self.pluginmanager.disable_plugin(plugin)
@export
- def force_recheck(self, torrent_ids):
+ def force_recheck(self, torrent_ids: List[str]) -> None:
"""Forces a data recheck on torrent_ids"""
for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].force_recheck()
@export
- def set_torrent_options(self, torrent_ids, options):
+ def set_torrent_options(
+ self, torrent_ids: List[str], options: Dict[str, Any]
+ ) -> None:
"""Sets the torrent options for torrent_ids
Args:
- torrent_ids (list): A list of torrent_ids to set the options for.
- options (dict): A dict of torrent options to set. See
+ torrent_ids: A list of torrent_ids to set the options for.
+ options: A dict of torrent options to set. See
``torrent.TorrentOptions`` class for valid keys.
"""
if 'owner' in options and not self.authmanager.has_account(options['owner']):
raise DelugeError('Username "%s" is not known.' % options['owner'])
- if isinstance(torrent_ids, string_types):
+ if isinstance(torrent_ids, str):
torrent_ids = [torrent_ids]
for torrent_id in torrent_ids:
self.torrentmanager[torrent_id].set_options(options)
@export
- def set_torrent_trackers(self, torrent_id, trackers):
+ def set_torrent_trackers(
+ self, torrent_id: str, trackers: List[Dict[str, Any]]
+ ) -> None:
"""Sets a torrents tracker list. trackers will be ``[{"url", "tier"}]``"""
return self.torrentmanager[torrent_id].set_trackers(trackers)
@export
- def get_magnet_uri(self, torrent_id):
+ def get_magnet_uri(self, torrent_id: str) -> str:
return self.torrentmanager[torrent_id].get_magnet_uri()
@deprecated
@@ -1061,7 +1053,7 @@ class Core(component.Component):
self.add_torrent_file(os.path.split(target)[1], filedump, options)
@export
- def upload_plugin(self, filename, filedump):
+ def upload_plugin(self, filename: str, filedump: Union[str, bytes]) -> None:
"""This method is used to upload new plugins to the daemon. It is used
when connecting to the daemon remotely and installing a new plugin on
the client side. ``plugin_data`` is a ``xmlrpc.Binary`` object of the file data,
@@ -1079,26 +1071,24 @@ class Core(component.Component):
component.get('CorePluginManager').scan_for_plugins()
@export
- def rescan_plugins(self):
- """
- Re-scans the plugin folders for new plugins
- """
+ def rescan_plugins(self) -> None:
+ """Re-scans the plugin folders for new plugins"""
component.get('CorePluginManager').scan_for_plugins()
@export
- def rename_files(self, torrent_id, filenames):
- """
- Rename files in ``torrent_id``. Since this is an asynchronous operation by
+ def rename_files(
+ self, torrent_id: str, filenames: List[Tuple[int, str]]
+ ) -> defer.Deferred:
+ """Rename files in ``torrent_id``. Since this is an asynchronous operation by
libtorrent, watch for the TorrentFileRenamedEvent to know when the
files have been renamed.
- :param torrent_id: the torrent_id to rename files
- :type torrent_id: string
- :param filenames: a list of index, filename pairs
- :type filenames: ((index, filename), ...)
-
- :raises InvalidTorrentError: if torrent_id is invalid
+ Args:
+ torrent_id: the torrent_id to rename files
+ filenames: a list of index, filename pairs
+ Raises:
+ InvalidTorrentError: if torrent_id is invalid
"""
if torrent_id not in self.torrentmanager.torrents:
raise InvalidTorrentError('torrent_id is not in session')
@@ -1109,21 +1099,20 @@ class Core(component.Component):
return task.deferLater(reactor, 0, rename)
@export
- def rename_folder(self, torrent_id, folder, new_folder):
- """
- Renames the 'folder' to 'new_folder' in 'torrent_id'. Watch for the
+ def rename_folder(
+ self, torrent_id: str, folder: str, new_folder: str
+ ) -> defer.Deferred:
+ """Renames the 'folder' to 'new_folder' in 'torrent_id'. Watch for the
TorrentFolderRenamedEvent which is emitted when the folder has been
renamed successfully.
- :param torrent_id: the torrent to rename folder in
- :type torrent_id: string
- :param folder: the folder to rename
- :type folder: string
- :param new_folder: the new folder name
- :type new_folder: string
-
- :raises InvalidTorrentError: if the torrent_id is invalid
+ Args:
+ torrent_id: the torrent to rename folder in
+ folder: the folder to rename
+ new_folder: the new folder name
+ Raises:
+ InvalidTorrentError: if the torrent_id is invalid
"""
if torrent_id not in self.torrentmanager.torrents:
raise InvalidTorrentError('torrent_id is not in session')
@@ -1131,7 +1120,7 @@ class Core(component.Component):
return self.torrentmanager[torrent_id].rename_folder(folder, new_folder)
@export
- def queue_top(self, torrent_ids):
+ def queue_top(self, torrent_ids: List[str]) -> None:
log.debug('Attempting to queue %s to top', torrent_ids)
# torrent_ids must be sorted in reverse before moving to preserve order
for torrent_id in sorted(
@@ -1145,7 +1134,7 @@ class Core(component.Component):
log.warning('torrent_id: %s does not exist in the queue', torrent_id)
@export
- def queue_up(self, torrent_ids):
+ def queue_up(self, torrent_ids: List[str]) -> None:
log.debug('Attempting to queue %s to up', torrent_ids)
torrents = (
(self.torrentmanager.get_queue_position(torrent_id), torrent_id)
@@ -1170,7 +1159,7 @@ class Core(component.Component):
prev_queue_position = queue_position
@export
- def queue_down(self, torrent_ids):
+ def queue_down(self, torrent_ids: List[str]) -> None:
log.debug('Attempting to queue %s to down', torrent_ids)
torrents = (
(self.torrentmanager.get_queue_position(torrent_id), torrent_id)
@@ -1195,7 +1184,7 @@ class Core(component.Component):
prev_queue_position = queue_position
@export
- def queue_bottom(self, torrent_ids):
+ def queue_bottom(self, torrent_ids: List[str]) -> None:
log.debug('Attempting to queue %s to bottom', torrent_ids)
# torrent_ids must be sorted before moving to preserve order
for torrent_id in sorted(
@@ -1209,17 +1198,15 @@ class Core(component.Component):
log.warning('torrent_id: %s does not exist in the queue', torrent_id)
@export
- def glob(self, path):
+ def glob(self, path: str) -> List[str]:
return glob.glob(path)
@export
- def test_listen_port(self):
- """
- Checks if the active port is open
-
- :returns: True if the port is open, False if not
- :rtype: bool
+ def test_listen_port(self) -> 'defer.Deferred[Optional[bool]]':
+ """Checks if the active port is open
+ Returns:
+ True if the port is open, False if not
"""
port = self.get_listen_port()
url = 'https://deluge-torrent.org/test_port.php?port=%s' % port
@@ -1238,18 +1225,17 @@ class Core(component.Component):
return d
@export
- def get_free_space(self, path=None):
- """
- Returns the number of free bytes at path
+ def get_free_space(self, path: str = None) -> int:
+ """Returns the number of free bytes at path
- :param path: the path to check free space at, if None, use the default download location
- :type path: string
-
- :returns: the number of free bytes at path
- :rtype: int
+ Args:
+ path: the path to check free space at, if None, use the default download location
- :raises InvalidPathError: if the path is invalid
+ Returns:
+ the number of free bytes at path
+ Raises:
+ InvalidPathError: if the path is invalid
"""
if not path:
path = self.config['download_location']
@@ -1262,46 +1248,40 @@ class Core(component.Component):
self.external_ip = external_ip
@export
- def get_external_ip(self):
- """
- Returns the external IP address received from libtorrent.
- """
+ def get_external_ip(self) -> str:
+ """Returns the external IP address received from libtorrent."""
return self.external_ip
@export
- def get_libtorrent_version(self):
- """
- Returns the libtorrent version.
-
- :returns: the version
- :rtype: string
+ def get_libtorrent_version(self) -> str:
+ """Returns the libtorrent version.
+ Returns:
+ the version
"""
return LT_VERSION
@export
- def get_completion_paths(self, args):
- """
- Returns the available path completions for the input value.
- """
+ def get_completion_paths(self, args: Dict[str, Any]) -> Dict[str, Any]:
+ """Returns the available path completions for the input value."""
return path_chooser_common.get_completion_paths(args)
@export(AUTH_LEVEL_ADMIN)
- def get_known_accounts(self):
+ def get_known_accounts(self) -> List[Dict[str, Any]]:
return self.authmanager.get_known_accounts()
@export(AUTH_LEVEL_NONE)
- def get_auth_levels_mappings(self):
+ def get_auth_levels_mappings(self) -> Tuple[Dict[str, int], Dict[int, str]]:
return (AUTH_LEVELS_MAPPING, AUTH_LEVELS_MAPPING_REVERSE)
@export(AUTH_LEVEL_ADMIN)
- def create_account(self, username, password, authlevel):
+ def create_account(self, username: str, password: str, authlevel: str) -> bool:
return self.authmanager.create_account(username, password, authlevel)
@export(AUTH_LEVEL_ADMIN)
- def update_account(self, username, password, authlevel):
+ def update_account(self, username: str, password: str, authlevel: str) -> bool:
return self.authmanager.update_account(username, password, authlevel)
@export(AUTH_LEVEL_ADMIN)
- def remove_account(self, username):
+ def remove_account(self, username: str) -> bool:
return self.authmanager.remove_account(username)
diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py
index 3c2ac643b..0185dd8cb 100644
--- a/deluge/core/daemon.py
+++ b/deluge/core/daemon.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -8,8 +7,6 @@
#
"""The Deluge daemon"""
-from __future__ import unicode_literals
-
import logging
import os
import socket
@@ -44,8 +41,8 @@ def is_daemon_running(pid_file):
try:
with open(pid_file) as _file:
- pid, port = [int(x) for x in _file.readline().strip().split(';')]
- except (EnvironmentError, ValueError):
+ pid, port = (int(x) for x in _file.readline().strip().split(';'))
+ except (OSError, ValueError):
return False
if is_process_running(pid):
@@ -53,7 +50,7 @@ def is_daemon_running(pid_file):
_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
_socket.connect(('127.0.0.1', port))
- except socket.error:
+ except OSError:
# Can't connect, so pid is not a deluged process.
return False
else:
@@ -62,7 +59,7 @@ def is_daemon_running(pid_file):
return True
-class Daemon(object):
+class Daemon:
"""The Deluge Daemon class"""
def __init__(
@@ -156,7 +153,7 @@ class Daemon(object):
pid = os.getpid()
log.debug('Storing pid %s & port %s in: %s', pid, self.port, self.pid_file)
with open(self.pid_file, 'w') as _file:
- _file.write('%s;%s\n' % (pid, self.port))
+ _file.write(f'{pid};{self.port}\n')
component.start()
diff --git a/deluge/core/daemon_entry.py b/deluge/core/daemon_entry.py
index 8b3746c43..c49fd2a35 100644
--- a/deluge/core/daemon_entry.py
+++ b/deluge/core/daemon_entry.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me>
@@ -7,8 +6,6 @@
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import os
import sys
from logging import DEBUG, FileHandler, getLogger
diff --git a/deluge/core/eventmanager.py b/deluge/core/eventmanager.py
index 5ba2989f9..d43847a77 100644
--- a/deluge/core/eventmanager.py
+++ b/deluge/core/eventmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
diff --git a/deluge/core/filtermanager.py b/deluge/core/filtermanager.py
index 90589ef1f..a60cc5bc4 100644
--- a/deluge/core/filtermanager.py
+++ b/deluge/core/filtermanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -7,12 +6,8 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
-from six import string_types
-
import deluge.component as component
from deluge.common import TORRENT_STATE
@@ -136,7 +131,7 @@ class FilterManager(component.Component):
# Sanitize input: filter-value must be a list of strings
for key, value in filter_dict.items():
- if isinstance(value, string_types):
+ if isinstance(value, str):
filter_dict[key] = [value]
# Optimized filter for id
diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py
index 7d2f3a1e8..0482b16e7 100644
--- a/deluge/core/pluginmanager.py
+++ b/deluge/core/pluginmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -9,8 +8,6 @@
"""PluginManager for Core"""
-from __future__ import unicode_literals
-
import logging
from twisted.internet import defer
diff --git a/deluge/core/preferencesmanager.py b/deluge/core/preferencesmanager.py
index c250130c0..7e5c207a1 100644
--- a/deluge/core/preferencesmanager.py
+++ b/deluge/core/preferencesmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2010 Andrew Resch <andrewresch@gmail.com>
#
@@ -8,13 +7,13 @@
#
-from __future__ import unicode_literals
-
import logging
import os
import platform
import random
import threading
+from urllib.parse import quote_plus
+from urllib.request import urlopen
from twisted.internet.task import LoopingCall
@@ -24,17 +23,14 @@ import deluge.configmanager
from deluge._libtorrent import lt
from deluge.event import ConfigValueChangedEvent
+GeoIP = None
try:
- import GeoIP
-except ImportError:
- GeoIP = None
-
-try:
- from urllib.parse import quote_plus
- from urllib.request import urlopen
+ from GeoIP import GeoIP
except ImportError:
- from urllib import quote_plus
- from urllib2 import urlopen
+ try:
+ from pygeoip import GeoIP
+ except ImportError:
+ pass
log = logging.getLogger(__name__)
@@ -202,7 +198,7 @@ class PreferencesManager(component.Component):
self.__set_listen_on()
def __set_listen_on(self):
- """ Set the ports and interface address to listen for incoming connections on."""
+ """Set the ports and interface address to listen for incoming connections on."""
if self.config['random_port']:
if not self.config['listen_random_port']:
self.config['listen_random_port'] = random.randrange(49152, 65525)
@@ -225,7 +221,7 @@ class PreferencesManager(component.Component):
self.config['listen_use_sys_port'],
)
interfaces = [
- '%s:%s' % (interface, port)
+ f'{interface}:{port}'
for port in range(listen_ports[0], listen_ports[1] + 1)
]
self.core.apply_session_settings(
@@ -400,7 +396,7 @@ class PreferencesManager(component.Component):
+ quote_plus(':'.join(self.config['enabled_plugins']))
)
urlopen(url)
- except IOError as ex:
+ except OSError as ex:
log.debug('Network error while trying to send info: %s', ex)
else:
self.config['info_sent'] = now
@@ -464,11 +460,9 @@ class PreferencesManager(component.Component):
# Load the GeoIP DB for country look-ups if available
if os.path.exists(geoipdb_path):
try:
- self.core.geoip_instance = GeoIP.open(
- geoipdb_path, GeoIP.GEOIP_STANDARD
- )
- except AttributeError:
- log.warning('GeoIP Unavailable')
+ self.core.geoip_instance = GeoIP(geoipdb_path, 0)
+ except Exception as ex:
+ log.warning('GeoIP Unavailable: %s', ex)
else:
log.warning('Unable to find GeoIP database file: %s', geoipdb_path)
diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py
index adb521901..d4ca5d19d 100644
--- a/deluge/core/rpcserver.py
+++ b/deluge/core/rpcserver.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008,2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -8,17 +7,14 @@
#
"""RPCServer Module"""
-from __future__ import unicode_literals
-
import logging
import os
-import stat
import sys
import traceback
from collections import namedtuple
from types import FunctionType
+from typing import Callable, TypeVar, overload
-from OpenSSL import crypto
from twisted.internet import defer, reactor
from twisted.internet.protocol import Factory, connectionDone
@@ -29,7 +25,7 @@ from deluge.core.authmanager import (
AUTH_LEVEL_DEFAULT,
AUTH_LEVEL_NONE,
)
-from deluge.crypto_utils import get_context_factory
+from deluge.crypto_utils import check_ssl_keys, get_context_factory
from deluge.error import (
DelugeError,
IncompatibleClient,
@@ -46,6 +42,18 @@ RPC_EVENT = 3
log = logging.getLogger(__name__)
+TCallable = TypeVar('TCallable', bound=Callable)
+
+
+@overload
+def export(func: TCallable) -> TCallable:
+ ...
+
+
+@overload
+def export(auth_level: int) -> Callable[[TCallable], TCallable]:
+ ...
+
def export(auth_level=AUTH_LEVEL_DEFAULT):
"""
@@ -69,7 +77,7 @@ def export(auth_level=AUTH_LEVEL_DEFAULT):
if func.__doc__:
if func.__doc__.endswith(' '):
indent = func.__doc__.split('\n')[-1]
- func.__doc__ += '\n{}'.format(indent)
+ func.__doc__ += f'\n{indent}'
else:
func.__doc__ += '\n\n'
func.__doc__ += rpc_text
@@ -114,7 +122,7 @@ def format_request(call):
class DelugeRPCProtocol(DelugeTransferProtocol):
def __init__(self):
- super(DelugeRPCProtocol, self).__init__()
+ super().__init__()
# namedtuple subclass with auth_level, username for the connected session.
self.AuthLevel = namedtuple('SessionAuthlevel', 'auth_level, username')
@@ -588,59 +596,3 @@ class RPCServer(component.Component):
def stop(self):
self.factory.state = 'stopping'
-
-
-def check_ssl_keys():
- """
- Check for SSL cert/key and create them if necessary
- """
- ssl_dir = deluge.configmanager.get_config_dir('ssl')
- if not os.path.exists(ssl_dir):
- # The ssl folder doesn't exist so we need to create it
- os.makedirs(ssl_dir)
- generate_ssl_keys()
- else:
- for f in ('daemon.pkey', 'daemon.cert'):
- if not os.path.exists(os.path.join(ssl_dir, f)):
- generate_ssl_keys()
- break
-
-
-def generate_ssl_keys():
- """
- This method generates a new SSL key/cert.
- """
- from deluge.common import PY2
-
- digest = 'sha256' if not PY2 else b'sha256'
-
- # Generate key pair
- pkey = crypto.PKey()
- pkey.generate_key(crypto.TYPE_RSA, 2048)
-
- # Generate cert request
- req = crypto.X509Req()
- subj = req.get_subject()
- setattr(subj, 'CN', 'Deluge Daemon')
- req.set_pubkey(pkey)
- req.sign(pkey, digest)
-
- # Generate certificate
- cert = crypto.X509()
- cert.set_serial_number(0)
- cert.gmtime_adj_notBefore(0)
- cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 3) # Three Years
- cert.set_issuer(req.get_subject())
- cert.set_subject(req.get_subject())
- cert.set_pubkey(req.get_pubkey())
- cert.sign(pkey, digest)
-
- # Write out files
- ssl_dir = deluge.configmanager.get_config_dir('ssl')
- with open(os.path.join(ssl_dir, 'daemon.pkey'), 'wb') as _file:
- _file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
- with open(os.path.join(ssl_dir, 'daemon.cert'), 'wb') as _file:
- _file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
- # Make the files only readable by this user
- for f in ('daemon.pkey', 'daemon.cert'):
- os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE)
diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py
index ede5200f0..57ec26f37 100644
--- a/deluge/core/torrent.py
+++ b/deluge/core/torrent.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -14,11 +13,12 @@ Attributes:
"""
-from __future__ import division, unicode_literals
-
import logging
import os
import socket
+import time
+from typing import Optional
+from urllib.parse import urlparse
from twisted.internet.defer import Deferred, DeferredList
@@ -34,18 +34,6 @@ from deluge.event import (
TorrentTrackerStatusEvent,
)
-try:
- from urllib.parse import urlparse
-except ImportError:
- # PY2 fallback
- from urlparse import urlparse # pylint: disable=ungrouped-imports
-
-try:
- from future_builtins import zip
-except ImportError:
- # Ignore on Py3.
- pass
-
log = logging.getLogger(__name__)
LT_TORRENT_STATE_MAP = {
@@ -94,7 +82,7 @@ def convert_lt_files(files):
"""Indexes and decodes files from libtorrent get_files().
Args:
- files (list): The libtorrent torrent files.
+ files (file_storage): The libtorrent torrent files.
Returns:
list of dict: The files.
@@ -109,18 +97,18 @@ def convert_lt_files(files):
}
"""
filelist = []
- for index, _file in enumerate(files):
+ for index in range(files.num_files()):
try:
- file_path = _file.path.decode('utf8')
+ file_path = files.file_path(index).decode('utf8')
except AttributeError:
- file_path = _file.path
+ file_path = files.file_path(index)
filelist.append(
{
'index': index,
'path': file_path.replace('\\', '/'),
- 'size': _file.size,
- 'offset': _file.offset,
+ 'size': files.file_size(index),
+ 'offset': files.file_offset(index),
}
)
@@ -161,7 +149,7 @@ class TorrentOptions(dict):
"""
def __init__(self):
- super(TorrentOptions, self).__init__()
+ super().__init__()
config = ConfigManager('core.conf').config
options_conf_map = {
'add_paused': 'add_paused',
@@ -191,14 +179,14 @@ class TorrentOptions(dict):
self['seed_mode'] = False
-class TorrentError(object):
+class TorrentError:
def __init__(self, error_message, was_paused=False, restart_to_resume=False):
self.error_message = error_message
self.was_paused = was_paused
self.restart_to_resume = restart_to_resume
-class Torrent(object):
+class Torrent:
"""Torrent holds information about torrents added to the libtorrent session.
Args:
@@ -248,9 +236,10 @@ class Torrent(object):
self.handle = handle
self.magnet = magnet
- self.status = self.handle.status()
+ self._status: Optional['lt.torrent_status'] = None
+ self._status_last_update: float = 0.0
- self.torrent_info = self.handle.get_torrent_info()
+ self.torrent_info = self.handle.torrent_file()
self.has_metadata = self.status.has_metadata
self.options = TorrentOptions()
@@ -281,7 +270,6 @@ class Torrent(object):
self.prev_status = {}
self.waiting_on_folder_rename = []
- self.update_status(self.handle.status())
self._create_status_funcs()
self.set_options(self.options)
self.update_state()
@@ -289,6 +277,18 @@ class Torrent(object):
if log.isEnabledFor(logging.DEBUG):
log.debug('Torrent object created.')
+ def _set_handle_flags(self, flag: lt.torrent_flags, set_flag: bool):
+ """set or unset a flag to the lt handle
+
+ Args:
+ flag (lt.torrent_flags): the flag to set/unset
+ set_flag (bool): True for setting the flag, False for unsetting it
+ """
+ if set_flag:
+ self.handle.set_flags(flag)
+ else:
+ self.handle.unset_flags(flag)
+
def on_metadata_received(self):
"""Process the metadata received alert for this torrent"""
self.has_metadata = True
@@ -373,7 +373,7 @@ class Torrent(object):
"""Sets maximum download speed for this torrent.
Args:
- m_up_speed (float): Maximum download speed in KiB/s.
+ m_down_speed (float): Maximum download speed in KiB/s.
"""
self.options['max_download_speed'] = m_down_speed
if m_down_speed < 0:
@@ -405,7 +405,7 @@ class Torrent(object):
return
# A list of priorities for each piece in the torrent
- priorities = self.handle.piece_priorities()
+ priorities = self.handle.get_piece_priorities()
def get_file_piece(idx, byte_offset):
return self.torrent_info.map_file(idx, byte_offset, 0).piece
@@ -438,7 +438,10 @@ class Torrent(object):
sequential (bool): Enable sequential downloading.
"""
self.options['sequential_download'] = sequential
- self.handle.set_sequential_download(sequential)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.sequential_download,
+ set_flag=sequential,
+ )
def set_auto_managed(self, auto_managed):
"""Set auto managed mode, i.e. will be started or queued automatically.
@@ -448,7 +451,10 @@ class Torrent(object):
"""
self.options['auto_managed'] = auto_managed
if not (self.status.paused and not self.status.auto_managed):
- self.handle.auto_managed(auto_managed)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=auto_managed,
+ )
self.update_state()
def set_super_seeding(self, super_seeding):
@@ -458,7 +464,10 @@ class Torrent(object):
super_seeding (bool): Enable super seeding.
"""
self.options['super_seeding'] = super_seeding
- self.handle.super_seeding(super_seeding)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.super_seeding,
+ set_flag=super_seeding,
+ )
def set_stop_ratio(self, stop_ratio):
"""The seeding ratio to stop (or remove) the torrent at.
@@ -519,7 +528,7 @@ class Torrent(object):
self.handle.prioritize_files(file_priorities)
else:
log.debug('Unable to set new file priorities.')
- file_priorities = self.handle.file_priorities()
+ file_priorities = self.handle.get_file_priorities()
if 0 in self.options['file_priorities']:
# Previously marked a file 'skip' so check for any 0's now >0.
@@ -569,7 +578,7 @@ class Torrent(object):
trackers (list of dicts): A list of trackers.
"""
if trackers is None:
- self.trackers = [tracker for tracker in self.handle.trackers()]
+ self.trackers = list(self.handle.trackers())
self.tracker_host = None
return
@@ -634,7 +643,7 @@ class Torrent(object):
def update_state(self):
"""Updates the state, based on libtorrent's torrent state"""
- status = self.handle.status()
+ status = self.get_lt_status()
session_paused = component.get('Core').session.is_paused()
old_state = self.state
self.set_status_message()
@@ -646,7 +655,10 @@ class Torrent(object):
elif status_error:
self.state = 'Error'
# auto-manage status will be reverted upon resuming.
- self.handle.auto_managed(False)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=False,
+ )
self.set_status_message(decode_bytes(status_error))
elif status.moving_storage:
self.state = 'Moving'
@@ -699,8 +711,11 @@ class Torrent(object):
restart_to_resume (bool, optional): Prevent resuming clearing the error, only restarting
session can resume.
"""
- status = self.handle.status()
- self.handle.auto_managed(False)
+ status = self.get_lt_status()
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=False,
+ )
self.forced_error = TorrentError(message, status.paused, restart_to_resume)
if not status.paused:
self.handle.pause()
@@ -714,7 +729,10 @@ class Torrent(object):
log.error('Restart deluge to clear this torrent error')
if not self.forced_error.was_paused and self.options['auto_managed']:
- self.handle.auto_managed(True)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=True,
+ )
self.forced_error = None
self.set_status_message('OK')
if update_state:
@@ -838,7 +856,7 @@ class Torrent(object):
'client': client,
'country': country,
'down_speed': peer.payload_down_speed,
- 'ip': '%s:%s' % (peer.ip[0], peer.ip[1]),
+ 'ip': f'{peer.ip[0]}:{peer.ip[1]}',
'progress': peer.progress,
'seed': peer.flags & peer.seed,
'up_speed': peer.payload_up_speed,
@@ -857,7 +875,7 @@ class Torrent(object):
def get_file_priorities(self):
"""Return the file priorities"""
- if not self.handle.has_metadata():
+ if not self.handle.status().has_metadata:
return []
if not self.options['file_priorities']:
@@ -910,7 +928,7 @@ class Torrent(object):
# Check if hostname is an IP address and just return it if that's the case
try:
socket.inet_aton(host)
- except socket.error:
+ except OSError:
pass
else:
# This is an IP address because an exception wasn't raised
@@ -946,10 +964,10 @@ class Torrent(object):
if self.has_metadata:
# Use the top-level folder as torrent name.
- filename = decode_bytes(self.torrent_info.file_at(0).path)
+ filename = decode_bytes(self.torrent_info.files().file_path(0))
name = filename.replace('\\', '/', 1).split('/', 1)[0]
else:
- name = decode_bytes(self.handle.name())
+ name = decode_bytes(self.handle.status().name)
if not name:
name = self.torrent_id
@@ -1008,7 +1026,7 @@ class Torrent(object):
dict: a dictionary of the status keys and their values
"""
if update:
- self.update_status(self.handle.status())
+ self.get_lt_status()
if all_keys:
keys = list(self.status_funcs)
@@ -1038,13 +1056,35 @@ class Torrent(object):
return status_dict
- def update_status(self, status):
+ def get_lt_status(self) -> 'lt.torrent_status':
+ """Get the torrent status fresh, not from cache.
+
+ This should be used when a guaranteed fresh status is needed rather than
+ `torrent.handle.status()` because it will update the cache as well.
+ """
+ self.status = self.handle.status()
+ return self.status
+
+ @property
+ def status(self) -> 'lt.torrent_status':
+ """Cached copy of the libtorrent status for this torrent.
+
+ If it has not been updated within the last five seconds, it will be
+ automatically refreshed.
+ """
+ if self._status_last_update < (time.time() - 5):
+ self.status = self.handle.status()
+ return self._status
+
+ @status.setter
+ def status(self, status: 'lt.torrent_status') -> None:
"""Updates the cached status.
Args:
- status (libtorrent.torrent_status): a libtorrent torrent status
+ status: a libtorrent torrent status
"""
- self.status = status
+ self._status = status
+ self._status_last_update = time.time()
def _create_status_funcs(self):
"""Creates the functions for getting torrent status"""
@@ -1166,7 +1206,10 @@ class Torrent(object):
"""
# Turn off auto-management so the torrent will not be unpaused by lt queueing
- self.handle.auto_managed(False)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=False,
+ )
if self.state == 'Error':
log.debug('Unable to pause torrent while in Error state')
elif self.status.paused:
@@ -1201,7 +1244,10 @@ class Torrent(object):
else:
# Check if torrent was originally being auto-managed.
if self.options['auto_managed']:
- self.handle.auto_managed(True)
+ self._set_handle_flags(
+ flag=lt.torrent_flags.auto_managed,
+ set_flag=True,
+ )
try:
self.handle.resume()
except RuntimeError as ex:
@@ -1305,7 +1351,7 @@ class Torrent(object):
try:
with open(filepath, 'wb') as save_file:
save_file.write(filedump)
- except IOError as ex:
+ except OSError as ex:
log.error('Unable to save torrent file to: %s', ex)
filepath = os.path.join(get_config_dir(), 'state', self.torrent_id + '.torrent')
diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py
index 365f37233..5609df4bd 100644
--- a/deluge/core/torrentmanager.py
+++ b/deluge/core/torrentmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -8,25 +7,23 @@
#
"""TorrentManager handles Torrent objects"""
-from __future__ import unicode_literals
-
import datetime
import logging
import operator
import os
+import pickle
import time
-from collections import namedtuple
+from base64 import b64encode
from tempfile import gettempdir
+from typing import Dict, List, NamedTuple, Tuple
-import six.moves.cPickle as pickle # noqa: N813
-from twisted.internet import defer, error, reactor, threads
+from twisted.internet import defer, reactor, threads
from twisted.internet.defer import Deferred, DeferredList
from twisted.internet.task import LoopingCall
import deluge.component as component
from deluge._libtorrent import LT_VERSION, lt
from deluge.common import (
- PY2,
VersionSplit,
archive_files,
decode_bytes,
@@ -36,6 +33,7 @@ from deluge.common import (
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.core.torrent import Torrent, TorrentOptions, sanitize_filepath
+from deluge.decorators import maybe_coroutine
from deluge.error import AddTorrentError, InvalidTorrentError
from deluge.event import (
ExternalIPEvent,
@@ -59,6 +57,11 @@ LT_DEFAULT_ADD_TORRENT_FLAGS = (
)
+class PrefetchQueueItem(NamedTuple):
+ alert_deferred: Deferred
+ result_queue: List[Deferred]
+
+
class TorrentState: # pylint: disable=old-style-class
"""Create a torrent state.
@@ -136,7 +139,8 @@ class TorrentManager(component.Component):
"""
- callLater = reactor.callLater # noqa: N815
+ # This is used in the test to mock out timeouts
+ clock = reactor
def __init__(self):
component.Component.__init__(
@@ -165,7 +169,7 @@ class TorrentManager(component.Component):
self.is_saving_state = False
self.save_resume_data_file_lock = defer.DeferredLock()
self.torrents_loading = {}
- self.prefetching_metadata = {}
+ self.prefetching_metadata: Dict[str, PrefetchQueueItem] = {}
# This is a map of torrent_ids to Deferreds used to track needed resume data.
# The Deferreds will be completed when resume data has been saved.
@@ -250,8 +254,8 @@ class TorrentManager(component.Component):
self.save_resume_data_timer.start(190, False)
self.prev_status_cleanup_loop.start(10)
- @defer.inlineCallbacks
- def stop(self):
+ @maybe_coroutine
+ async def stop(self):
# Stop timers
if self.save_state_timer.running:
self.save_state_timer.stop()
@@ -263,11 +267,11 @@ class TorrentManager(component.Component):
self.prev_status_cleanup_loop.stop()
# Save state on shutdown
- yield self.save_state()
+ await self.save_state()
self.session.pause()
- result = yield self.save_resume_data(flush_disk_cache=True)
+ result = await self.save_resume_data(flush_disk_cache=True)
# Remove the temp_file to signify successfully saved state
if result and os.path.isfile(self.temp_file):
os.remove(self.temp_file)
@@ -281,11 +285,6 @@ class TorrentManager(component.Component):
'Paused',
'Queued',
):
- # If the global setting is set, but the per-torrent isn't...
- # Just skip to the next torrent.
- # This is so that a user can turn-off the stop at ratio option on a per-torrent basis
- if not torrent.options['stop_at_ratio']:
- continue
if (
torrent.get_ratio() >= torrent.options['stop_ratio']
and torrent.is_finished
@@ -293,7 +292,7 @@ class TorrentManager(component.Component):
if torrent.options['remove_at_ratio']:
self.remove(torrent_id)
break
- if not torrent.handle.status().paused:
+ if not torrent.status.paused:
torrent.pause()
def __getitem__(self, torrent_id):
@@ -346,26 +345,28 @@ class TorrentManager(component.Component):
else:
return torrent_info
- def prefetch_metadata(self, magnet, timeout):
+ @maybe_coroutine
+ async def prefetch_metadata(self, magnet: str, timeout: int) -> Tuple[str, bytes]:
"""Download the metadata for a magnet URI.
Args:
- magnet (str): A magnet URI to download the metadata for.
- timeout (int): Number of seconds to wait before canceling.
+ magnet: A magnet URI to download the metadata for.
+ timeout: Number of seconds to wait before canceling.
Returns:
- Deferred: A tuple of (torrent_id (str), metadata (dict))
+ A tuple of (torrent_id, metadata)
"""
torrent_id = get_magnet_info(magnet)['info_hash']
if torrent_id in self.prefetching_metadata:
- return self.prefetching_metadata[torrent_id].defer
+ d = Deferred()
+ self.prefetching_metadata[torrent_id].result_queue.append(d)
+ return await d
- add_torrent_params = {}
- add_torrent_params['save_path'] = gettempdir()
- add_torrent_params['url'] = magnet.strip().encode('utf8')
- add_torrent_params['flags'] = (
+ add_torrent_params = lt.parse_magnet_uri(magnet)
+ add_torrent_params.save_path = gettempdir()
+ add_torrent_params.flags = (
(
LT_DEFAULT_ADD_TORRENT_FLAGS
| lt.add_torrent_params_flags_t.flag_duplicate_is_error
@@ -379,33 +380,29 @@ class TorrentManager(component.Component):
d = Deferred()
# Cancel the defer if timeout reached.
- defer_timeout = self.callLater(timeout, d.cancel)
- d.addBoth(self.on_prefetch_metadata, torrent_id, defer_timeout)
- Prefetch = namedtuple('Prefetch', 'defer handle')
- self.prefetching_metadata[torrent_id] = Prefetch(defer=d, handle=torrent_handle)
- return d
+ d.addTimeout(timeout, self.clock)
+ self.prefetching_metadata[torrent_id] = PrefetchQueueItem(d, [])
- def on_prefetch_metadata(self, torrent_info, torrent_id, defer_timeout):
- # Cancel reactor.callLater.
try:
- defer_timeout.cancel()
- except error.AlreadyCalled:
- pass
-
- log.debug('remove prefetch magnet from session')
- try:
- torrent_handle = self.prefetching_metadata.pop(torrent_id).handle
- except KeyError:
- pass
+ torrent_info = await d
+ except (defer.TimeoutError, defer.CancelledError):
+ log.debug(f'Prefetching metadata for {torrent_id} timed out or cancelled.')
+ metadata = b''
else:
- self.session.remove_torrent(torrent_handle, 1)
-
- metadata = None
- if isinstance(torrent_info, lt.torrent_info):
log.debug('prefetch metadata received')
- metadata = lt.bdecode(torrent_info.metadata())
+ if VersionSplit(LT_VERSION) < VersionSplit('2.0.0.0'):
+ metadata = torrent_info.metadata()
+ else:
+ metadata = torrent_info.info_section()
- return torrent_id, metadata
+ log.debug('remove prefetch magnet from session')
+ result_queue = self.prefetching_metadata.pop(torrent_id).result_queue
+ self.session.remove_torrent(torrent_handle, 1)
+ result = torrent_id, b64encode(metadata)
+
+ for d in result_queue:
+ d.callback(result)
+ return result
def _build_torrent_options(self, options):
"""Load default options and update if needed."""
@@ -438,14 +435,10 @@ class TorrentManager(component.Component):
elif magnet:
magnet_info = get_magnet_info(magnet)
if magnet_info:
- add_torrent_params['url'] = magnet.strip().encode('utf8')
add_torrent_params['name'] = magnet_info['name']
+ add_torrent_params['trackers'] = list(magnet_info['trackers'])
torrent_id = magnet_info['info_hash']
- # Workaround lt 1.2 bug for magnet resume data with no metadata
- if resume_data and VersionSplit(LT_VERSION) >= VersionSplit('1.2.10.0'):
- add_torrent_params['info_hash'] = bytes(
- bytearray.fromhex(torrent_id)
- )
+ add_torrent_params['info_hash'] = bytes(bytearray.fromhex(torrent_id))
else:
raise AddTorrentError(
'Unable to add magnet, invalid magnet info: %s' % magnet
@@ -460,7 +453,7 @@ class TorrentManager(component.Component):
raise AddTorrentError('Torrent already being added (%s).' % torrent_id)
elif torrent_id in self.prefetching_metadata:
# Cancel and remove metadata fetching torrent.
- self.prefetching_metadata[torrent_id].defer.cancel()
+ self.prefetching_metadata[torrent_id].alert_deferred.cancel()
# Check for renamed files and if so, rename them in the torrent_info before adding.
if options['mapped_files'] and torrent_info:
@@ -821,12 +814,9 @@ class TorrentManager(component.Component):
try:
with open(filepath, 'rb') as _file:
- if PY2:
- state = pickle.load(_file)
- else:
- state = pickle.load(_file, encoding='utf8')
- except (IOError, EOFError, pickle.UnpicklingError) as ex:
- message = 'Unable to load {}: {}'.format(filepath, ex)
+ state = pickle.load(_file, encoding='utf8')
+ except (OSError, EOFError, pickle.UnpicklingError) as ex:
+ message = f'Unable to load {filepath}: {ex}'
log.error(message)
if not filepath.endswith('.bak'):
self.archive_state(message)
@@ -1082,7 +1072,7 @@ class TorrentManager(component.Component):
try:
with open(_filepath, 'rb') as _file:
resume_data = lt.bdecode(_file.read())
- except (IOError, EOFError, RuntimeError) as ex:
+ except (OSError, EOFError, RuntimeError) as ex:
if self.torrents:
log.warning('Unable to load %s: %s', _filepath, ex)
resume_data = None
@@ -1366,10 +1356,8 @@ class TorrentManager(component.Component):
torrent.set_tracker_status('Announce OK')
# Check for peer information from the tracker, if none then send a scrape request.
- if (
- alert.handle.status().num_complete == -1
- or alert.handle.status().num_incomplete == -1
- ):
+ torrent.get_lt_status()
+ if torrent.status.num_complete == -1 or torrent.status.num_incomplete == -1:
torrent.scrape_tracker()
def on_alert_tracker_announce(self, alert):
@@ -1404,22 +1392,18 @@ class TorrentManager(component.Component):
log.debug(
'Tracker Error Alert: %s [%s]', decode_bytes(alert.message()), error_message
)
- if VersionSplit(LT_VERSION) >= VersionSplit('1.2.0.0'):
- # libtorrent 1.2 added endpoint struct to each tracker. to prevent false updates
- # we will need to verify that at least one endpoint to the errored tracker is working
- for tracker in torrent.handle.trackers():
- if tracker['url'] == alert.url:
- if any(
- endpoint['last_error']['value'] == 0
- for endpoint in tracker['endpoints']
- ):
- torrent.set_tracker_status('Announce OK')
- else:
- torrent.set_tracker_status('Error: ' + error_message)
- break
- else:
- # preserve old functionality for libtorrent < 1.2
- torrent.set_tracker_status('Error: ' + error_message)
+ # libtorrent 1.2 added endpoint struct to each tracker. to prevent false updates
+ # we will need to verify that at least one endpoint to the errored tracker is working
+ for tracker in torrent.handle.trackers():
+ if tracker['url'] == alert.url:
+ if any(
+ endpoint['last_error']['value'] == 0
+ for endpoint in tracker['endpoints']
+ ):
+ torrent.set_tracker_status('Announce OK')
+ else:
+ torrent.set_tracker_status('Error: ' + error_message)
+ break
def on_alert_storage_moved(self, alert):
"""Alert handler for libtorrent storage_moved_alert"""
@@ -1493,7 +1477,9 @@ class TorrentManager(component.Component):
return
if torrent_id in self.torrents:
# libtorrent add_torrent expects bencoded resume_data.
- self.resume_data[torrent_id] = lt.bencode(alert.resume_data)
+ self.resume_data[torrent_id] = lt.bencode(
+ lt.write_resume_data(alert.params)
+ )
if torrent_id in self.waiting_on_resume_data:
self.waiting_on_resume_data[torrent_id].callback(None)
@@ -1575,7 +1561,7 @@ class TorrentManager(component.Component):
# Try callback to prefetch_metadata method.
try:
- d = self.prefetching_metadata[torrent_id].defer
+ d = self.prefetching_metadata[torrent_id].alert_deferred
except KeyError:
pass
else:
@@ -1621,7 +1607,7 @@ class TorrentManager(component.Component):
except RuntimeError:
continue
if torrent_id in self.torrents:
- self.torrents[torrent_id].update_status(t_status)
+ self.torrents[torrent_id].status = t_status
self.handle_torrents_status_callback(self.torrents_status_requests.pop())
diff --git a/deluge/crypto_utils.py b/deluge/crypto_utils.py
index 7672efa71..d636c05e7 100644
--- a/deluge/crypto_utils.py
+++ b/deluge/crypto_utils.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007,2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,10 @@
# See LICENSE for more details.
#
-from __future__ import division, print_function, unicode_literals
+import os
+import stat
+from OpenSSL import crypto
from OpenSSL.crypto import FILETYPE_PEM
from twisted.internet.ssl import (
AcceptableCiphers,
@@ -18,6 +19,8 @@ from twisted.internet.ssl import (
TLSVersion,
)
+import deluge.configmanager
+
# A TLS ciphers list.
# Sources for more information on TLS ciphers:
# - https://wiki.mozilla.org/Security/Server_Side_TLS
@@ -77,3 +80,57 @@ def get_context_factory(cert_path, pkey_path):
ctx.set_options(SSL_OP_NO_RENEGOTIATION)
return cert_options
+
+
+def check_ssl_keys():
+ """
+ Check for SSL cert/key and create them if necessary
+ """
+ ssl_dir = deluge.configmanager.get_config_dir('ssl')
+ if not os.path.exists(ssl_dir):
+ # The ssl folder doesn't exist so we need to create it
+ os.makedirs(ssl_dir)
+ generate_ssl_keys()
+ else:
+ for f in ('daemon.pkey', 'daemon.cert'):
+ if not os.path.exists(os.path.join(ssl_dir, f)):
+ generate_ssl_keys()
+ break
+
+
+def generate_ssl_keys():
+ """
+ This method generates a new SSL key/cert.
+ """
+ digest = 'sha256'
+
+ # Generate key pair
+ pkey = crypto.PKey()
+ pkey.generate_key(crypto.TYPE_RSA, 2048)
+
+ # Generate cert request
+ req = crypto.X509Req()
+ subj = req.get_subject()
+ setattr(subj, 'CN', 'Deluge Daemon')
+ req.set_pubkey(pkey)
+ req.sign(pkey, digest)
+
+ # Generate certificate
+ cert = crypto.X509()
+ cert.set_serial_number(0)
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(60 * 60 * 24 * 365 * 3) # Three Years
+ cert.set_issuer(req.get_subject())
+ cert.set_subject(req.get_subject())
+ cert.set_pubkey(req.get_pubkey())
+ cert.sign(pkey, digest)
+
+ # Write out files
+ ssl_dir = deluge.configmanager.get_config_dir('ssl')
+ with open(os.path.join(ssl_dir, 'daemon.pkey'), 'wb') as _file:
+ _file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
+ with open(os.path.join(ssl_dir, 'daemon.cert'), 'wb') as _file:
+ _file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
+ # Make the files only readable by this user
+ for f in ('daemon.pkey', 'daemon.cert'):
+ os.chmod(os.path.join(ssl_dir, f), stat.S_IREAD | stat.S_IWRITE)
diff --git a/deluge/decorators.py b/deluge/decorators.py
index a601bf7e0..2f9fcd7d3 100644
--- a/deluge/decorators.py
+++ b/deluge/decorators.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -7,12 +6,13 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import inspect
import re
import warnings
from functools import wraps
+from typing import Any, Callable, Coroutine, TypeVar
+
+from twisted.internet import defer
def proxy(proxy_func):
@@ -127,7 +127,7 @@ def _overrides(stack, method, explicit_base_classes=None):
% (
method.__name__,
cls,
- 'File: %s:%s' % (stack[1][1], stack[1][2]),
+ f'File: {stack[1][1]}:{stack[1][2]}',
)
)
@@ -137,7 +137,7 @@ def _overrides(stack, method, explicit_base_classes=None):
% (
method.__name__,
check_classes,
- 'File: %s:%s' % (stack[1][1], stack[1][2]),
+ f'File: {stack[1][1]}:{stack[1][2]}',
)
)
return method
@@ -154,7 +154,7 @@ def deprecated(func):
def depr_func(*args, **kwargs):
warnings.simplefilter('always', DeprecationWarning) # Turn off filter
warnings.warn(
- 'Call to deprecated function {}.'.format(func.__name__),
+ f'Call to deprecated function {func.__name__}.',
category=DeprecationWarning,
stacklevel=2,
)
@@ -162,3 +162,57 @@ def deprecated(func):
return func(*args, **kwargs)
return depr_func
+
+
+class CoroutineDeferred(defer.Deferred):
+ """Wraps a coroutine in a Deferred.
+ It will dynamically pass through the underlying coroutine without wrapping where apporpriate."""
+
+ def __init__(self, coro: Coroutine):
+ # Delay this import to make sure a reactor was installed first
+ from twisted.internet import reactor
+
+ super().__init__()
+ self.coro = coro
+ self.awaited = None
+ self.activate_deferred = reactor.callLater(0, self.activate)
+
+ def __await__(self):
+ if self.awaited in [None, True]:
+ self.awaited = True
+ return self.coro.__await__()
+ # Already in deferred mode
+ return super().__await__()
+
+ def activate(self):
+ """If the result wasn't awaited before the next context switch, we turn it into a deferred."""
+ if self.awaited is None:
+ self.awaited = False
+ try:
+ d = defer.Deferred.fromCoroutine(self.coro)
+ except AttributeError:
+ # Fallback for Twisted <= 21.2 without fromCoroutine
+ d = defer.ensureDeferred(self.coro)
+ d.chainDeferred(self)
+
+ def addCallbacks(self, *args, **kwargs): # noqa: N802
+ assert not self.awaited, 'Cannot add callbacks to an already awaited coroutine.'
+ self.activate()
+ return super().addCallbacks(*args, **kwargs)
+
+
+_RetT = TypeVar('_RetT')
+
+
+def maybe_coroutine(
+ f: Callable[..., Coroutine[Any, Any, _RetT]]
+) -> 'Callable[..., defer.Deferred[_RetT]]':
+ """Wraps a coroutine function to make it usable as a normal function that returns a Deferred."""
+
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ # Uncomment for quick testing to make sure CoroutineDeferred magic isn't at fault
+ # return defer.ensureDeferred(f(*args, **kwargs))
+ return CoroutineDeferred(f(*args, **kwargs))
+
+ return wrapper
diff --git a/deluge/error.py b/deluge/error.py
index 46e8e0cf1..d542dc2c0 100644
--- a/deluge/error.py
+++ b/deluge/error.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@@ -9,18 +8,15 @@
#
-from __future__ import unicode_literals
-
-
class DelugeError(Exception):
def __new__(cls, *args, **kwargs):
- inst = super(DelugeError, cls).__new__(cls, *args, **kwargs)
+ inst = super().__new__(cls, *args, **kwargs)
inst._args = args
inst._kwargs = kwargs
return inst
def __init__(self, message=None):
- super(DelugeError, self).__init__(message)
+ super().__init__(message)
self.message = message
def __str__(self):
@@ -45,12 +41,12 @@ class InvalidPathError(DelugeError):
class WrappedException(DelugeError):
def __init__(self, message, exception_type, traceback):
- super(WrappedException, self).__init__(message)
+ super().__init__(message)
self.type = exception_type
self.traceback = traceback
def __str__(self):
- return '%s\n%s' % (self.message, self.traceback)
+ return f'{self.message}\n{self.traceback}'
class _ClientSideRecreateError(DelugeError):
@@ -64,7 +60,7 @@ class IncompatibleClient(_ClientSideRecreateError):
'Your deluge client is not compatible with the daemon. '
'Please upgrade your client to %(daemon_version)s'
) % {'daemon_version': self.daemon_version}
- super(IncompatibleClient, self).__init__(message=msg)
+ super().__init__(message=msg)
class NotAuthorizedError(_ClientSideRecreateError):
@@ -73,14 +69,14 @@ class NotAuthorizedError(_ClientSideRecreateError):
'current_level': current_level,
'required_level': required_level,
}
- super(NotAuthorizedError, self).__init__(message=msg)
+ super().__init__(message=msg)
self.current_level = current_level
self.required_level = required_level
class _UsernameBasedPasstroughError(_ClientSideRecreateError):
def __init__(self, message, username):
- super(_UsernameBasedPasstroughError, self).__init__(message)
+ super().__init__(message)
self.username = username
diff --git a/deluge/event.py b/deluge/event.py
index c5d5ff910..38fc32ff8 100644
--- a/deluge/event.py
+++ b/deluge/event.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -14,10 +13,6 @@ This module describes the types of events that can be generated by the daemon
and subsequently emitted to the clients.
"""
-from __future__ import unicode_literals
-
-import six
-
known_events = {}
@@ -27,12 +22,12 @@ class DelugeEventMetaClass(type):
"""
def __init__(cls, name, bases, dct): # pylint: disable=bad-mcs-method-argument
- super(DelugeEventMetaClass, cls).__init__(name, bases, dct)
+ super().__init__(name, bases, dct)
if name != 'DelugeEvent':
known_events[name] = cls
-class DelugeEvent(six.with_metaclass(DelugeEventMetaClass, object)):
+class DelugeEvent(metaclass=DelugeEventMetaClass):
"""
The base class for all events.
diff --git a/deluge/httpdownloader.py b/deluge/httpdownloader.py
index f0fe09ec0..700ade06b 100644
--- a/deluge/httpdownloader.py
+++ b/deluge/httpdownloader.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import cgi
import logging
import os.path
@@ -19,7 +16,7 @@ from twisted.internet.defer import Deferred
from twisted.python.failure import Failure
from twisted.web import client, http
from twisted.web._newclient import HTTPClientParser
-from twisted.web.error import PageRedirect
+from twisted.web.error import Error, PageRedirect
from twisted.web.http_headers import Headers
from twisted.web.iweb import IAgent
from zope.interface import implementer
@@ -40,11 +37,11 @@ class CompressionDecoderProtocol(client._GzipProtocol):
"""A compression decoder protocol for CompressionDecoder."""
def __init__(self, protocol, response):
- super(CompressionDecoderProtocol, self).__init__(protocol, response)
+ super().__init__(protocol, response)
self._zlibDecompress = zlib.decompressobj(32 + zlib.MAX_WBITS)
-class BodyHandler(HTTPClientParser, object):
+class BodyHandler(HTTPClientParser):
"""An HTTP parser that saves the response to a file."""
def __init__(self, request, finished, length, agent, encoding=None):
@@ -56,7 +53,7 @@ class BodyHandler(HTTPClientParser, object):
length (int): The length of the response.
agent (t.w.i.IAgent): The agent from which the request was sent.
"""
- super(BodyHandler, self).__init__(request, finished)
+ super().__init__(request, finished)
self.agent = agent
self.finished = finished
self.total_length = length
@@ -76,12 +73,12 @@ class BodyHandler(HTTPClientParser, object):
with open(self.agent.filename, 'wb') as _file:
_file.write(self.data)
self.finished.callback(self.agent.filename)
- self.state = u'DONE'
+ self.state = 'DONE'
HTTPClientParser.connectionLost(self, reason)
@implementer(IAgent)
-class HTTPDownloaderAgent(object):
+class HTTPDownloaderAgent:
"""A File Downloader Agent."""
def __init__(
@@ -125,6 +122,9 @@ class HTTPDownloaderAgent(object):
location = response.headers.getRawHeaders(b'location')[0]
error = PageRedirect(response.code, location=location)
finished.errback(Failure(error))
+ elif response.code >= 400:
+ error = Error(response.code)
+ finished.errback(Failure(error))
else:
headers = response.headers
body_length = int(headers.getRawHeaders(b'content-length', default=[0])[0])
@@ -146,7 +146,7 @@ class HTTPDownloaderAgent(object):
fileext = os.path.splitext(new_file_name)[1]
while os.path.isfile(new_file_name):
# Increment filename if already exists
- new_file_name = '%s-%s%s' % (fileroot, count, fileext)
+ new_file_name = f'{fileroot}-{count}{fileext}'
count += 1
self.filename = new_file_name
diff --git a/deluge/i18n/languages.py b/deluge/i18n/languages.py
index 49dc53026..5673c7116 100644
--- a/deluge/i18n/languages.py
+++ b/deluge/i18n/languages.py
@@ -1,10 +1,7 @@
-# -*- coding: utf-8 -*-
#
# This file is public domain.
#
-from __future__ import unicode_literals
-
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
diff --git a/deluge/i18n/util.py b/deluge/i18n/util.py
index 6ed4b97da..df2e3ab8a 100644
--- a/deluge/i18n/util.py
+++ b/deluge/i18n/util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007,2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,7 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
+import builtins
import ctypes
import gettext
import locale
@@ -17,8 +15,6 @@ import os
import sys
from glob import glob
-from six.moves import builtins
-
import deluge.common
from .languages import LANGUAGES
@@ -80,7 +76,7 @@ def set_language(lang):
translation = gettext.translation(
'deluge', localedir=get_translations_path(), languages=[lang]
)
- except IOError:
+ except OSError:
log.warning('Unable to find translation (.mo) to set language: %s', lang)
else:
translation.install()
@@ -113,19 +109,17 @@ def setup_translation():
gettext.bindtextdomain(I18N_DOMAIN, translations_path)
gettext.textdomain(I18N_DOMAIN)
- # Workaround for Python 2 unicode gettext (keyword removed in Py3).
- kwargs = {} if not deluge.common.PY2 else {'unicode': True}
-
- gettext.install(I18N_DOMAIN, translations_path, names=['ngettext'], **kwargs)
+ gettext.install(I18N_DOMAIN, translations_path, names=['ngettext'])
builtins.__dict__['_n'] = builtins.__dict__['ngettext']
def load_libintl(libintls):
errors = []
+ libintl = None
for library in libintls:
try:
libintl = ctypes.cdll.LoadLibrary(library)
except OSError as ex:
- errors.append(ex)
+ errors.append(str(ex))
else:
break
diff --git a/deluge/log.py b/deluge/log.py
index 99838529f..9ac0e27d5 100644
--- a/deluge/log.py
+++ b/deluge/log.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me>
@@ -9,8 +8,6 @@
#
"""Logging functions"""
-from __future__ import unicode_literals
-
import inspect
import logging
import logging.handlers
@@ -39,7 +36,7 @@ MAX_LOGGER_NAME_LENGTH = 10
class Logging(LoggingLoggerClass):
def __init__(self, logger_name):
- super(Logging, self).__init__(logger_name)
+ super().__init__(logger_name)
# This makes module name padding increase to the biggest module name
# so that logs keep readability.
@@ -54,39 +51,31 @@ class Logging(LoggingLoggerClass):
)
)
- @defer.inlineCallbacks
def garbage(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.log(self, 1, msg, *args, **kwargs)
+ LoggingLoggerClass.log(self, 1, msg, *args, **kwargs)
- @defer.inlineCallbacks
def trace(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.log(self, 5, msg, *args, **kwargs)
+ LoggingLoggerClass.log(self, 5, msg, *args, **kwargs)
- @defer.inlineCallbacks
def debug(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.debug(self, msg, *args, **kwargs)
+ LoggingLoggerClass.debug(self, msg, *args, **kwargs)
- @defer.inlineCallbacks
def info(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.info(self, msg, *args, **kwargs)
+ LoggingLoggerClass.info(self, msg, *args, **kwargs)
- @defer.inlineCallbacks
def warning(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.warning(self, msg, *args, **kwargs)
+ LoggingLoggerClass.warning(self, msg, *args, **kwargs)
warn = warning
- @defer.inlineCallbacks
def error(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.error(self, msg, *args, **kwargs)
+ LoggingLoggerClass.error(self, msg, *args, **kwargs)
- @defer.inlineCallbacks
def critical(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.critical(self, msg, *args, **kwargs)
+ LoggingLoggerClass.critical(self, msg, *args, **kwargs)
- @defer.inlineCallbacks
def exception(self, msg, *args, **kwargs):
- yield LoggingLoggerClass.exception(self, msg, *args, **kwargs)
+ LoggingLoggerClass.exception(self, msg, *args, **kwargs)
def findCaller(self, *args, **kwargs): # NOQA: N802
f = logging.currentframe().f_back
@@ -102,10 +91,7 @@ class Logging(LoggingLoggerClass):
continue
rv = (co.co_filename, f.f_lineno, co.co_name, None)
break
- if common.PY2:
- return rv[:-1]
- else:
- return rv
+ return rv
levels = {
@@ -161,7 +147,12 @@ def setup_logger(
handler_cls = getattr(
logging.handlers, 'WatchedFileHandler', logging.FileHandler
)
- handler = handler_cls(filename, mode=filemode, encoding='utf-8')
+ try:
+ handler = handler_cls(filename, mode=filemode, encoding='utf-8')
+ except FileNotFoundError:
+ handler = logging.StreamHandler(stream=output_stream)
+ log = logging.getLogger(__name__)
+ log.error(f'Unable to write to log file `{filename}`')
else:
handler = logging.StreamHandler(stream=output_stream)
@@ -243,7 +234,7 @@ def tweak_logging_levels():
log.warning(
'logging.conf found! tweaking logging levels from %s', logging_config_file
)
- with open(logging_config_file, 'r') as _file:
+ with open(logging_config_file) as _file:
for line in _file:
if line.strip().startswith('#'):
continue
@@ -314,7 +305,7 @@ Triggering code:
"""
-class _BackwardsCompatibleLOG(object):
+class _BackwardsCompatibleLOG:
def __getattribute__(self, name):
import warnings
diff --git a/deluge/maketorrent.py b/deluge/maketorrent.py
index 528638e43..c0051cae0 100644
--- a/deluge/maketorrent.py
+++ b/deluge/maketorrent.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import os
from hashlib import sha1 as sha
@@ -32,7 +29,7 @@ class InvalidPieceSize(Exception):
pass
-class TorrentMetadata(object):
+class TorrentMetadata:
"""This class is used to create .torrent files.
Examples:
diff --git a/deluge/metafile.py b/deluge/metafile.py
index 8c28c7e91..cd6545a75 100644
--- a/deluge/metafile.py
+++ b/deluge/metafile.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Original file from BitTorrent-5.3-GPL.tar.gz
# Copyright (C) Bram Cohen
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
import os.path
import time
@@ -44,7 +41,7 @@ def dummy(*v):
pass
-class RemoteFileProgress(object):
+class RemoteFileProgress:
def __init__(self, session_id):
self.session_id = session_id
diff --git a/deluge/path_chooser_common.py b/deluge/path_chooser_common.py
index 0f93feef6..0ea92341c 100644
--- a/deluge/path_chooser_common.py
+++ b/deluge/path_chooser_common.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Bro <bro.development@gmail.com>
#
@@ -8,12 +7,8 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os
-from deluge.common import PY2
-
def is_hidden(filepath):
def has_hidden_attribute(filepath):
@@ -45,7 +40,7 @@ def get_completion_paths(args):
:param args: options
:type args: dict
:returns: the args argument containing the available completions for the completion_text
- :rtype: list
+ :rtype: dict
"""
args['paths'] = []
@@ -54,10 +49,7 @@ def get_completion_paths(args):
def get_subdirs(dirname):
try:
- if PY2:
- return os.walk(dirname).__next__[1]
- else:
- return next(os.walk(dirname))[1]
+ return next(os.walk(dirname))[1]
except StopIteration:
# Invalid dirname
return []
diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py
index 2c1744555..835dbb268 100644
--- a/deluge/pluginmanagerbase.py
+++ b/deluge/pluginmanagerbase.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -9,8 +8,7 @@
"""PluginManagerBase"""
-from __future__ import unicode_literals
-
+import email
import logging
import os.path
@@ -37,7 +35,7 @@ METADATA_KEYS = [
]
DEPRECATION_WARNING = """
-The plugin %s is not using the "deluge.plugins" namespace.
+The plugin %s is not using the "deluge_" namespace.
In order to avoid package name clashes between regular python packages and
deluge plugins, the way deluge plugins should be created has changed.
If you're seeing this message and you're not the developer of the plugin which
@@ -47,7 +45,7 @@ git repository to have an idea of what needs to be changed.
"""
-class PluginManagerBase(object):
+class PluginManagerBase:
"""PluginManagerBase is a base class for PluginManagers to inherit"""
def __init__(self, config_file, entry_name):
@@ -164,7 +162,7 @@ class PluginManagerBase(object):
log.exception(ex)
return_d = defer.fail(False)
- if not instance.__module__.startswith('deluge.plugins.'):
+ if not instance.__module__.startswith('deluge_'):
import warnings
warnings.warn_explicit(
@@ -257,28 +255,25 @@ class PluginManagerBase(object):
def get_plugin_info(self, name):
"""Returns a dictionary of plugin info from the metadata"""
- info = {}.fromkeys(METADATA_KEYS)
- last_header = ''
- cont_lines = []
- # Missing plugin info
+
if not self.pkg_env[name]:
log.warning('Failed to retrieve info for plugin: %s', name)
- for k in info:
- info[k] = 'not available'
+ info = {}.fromkeys(METADATA_KEYS, '')
+ info['Name'] = info['Version'] = 'not available'
return info
- for line in self.pkg_env[name][0].get_metadata('PKG-INFO').splitlines():
- if not line:
- continue
- if line[0] in ' \t' and (
- len(line.split(':', 1)) == 1 or line.split(':', 1)[0] not in info
- ):
- # This is a continuation
- cont_lines.append(line.strip())
- else:
- if cont_lines:
- info[last_header] = '\n'.join(cont_lines).strip()
- cont_lines = []
- if line.split(':', 1)[0] in info:
- last_header = line.split(':', 1)[0]
- info[last_header] = line.split(':', 1)[1].strip()
+
+ pkg_info = self.pkg_env[name][0].get_metadata('PKG-INFO')
+ return self.parse_pkg_info(pkg_info)
+
+ @staticmethod
+ def parse_pkg_info(pkg_info):
+ metadata_msg = email.message_from_string(pkg_info)
+ metadata_ver = metadata_msg.get('Metadata-Version')
+
+ info = {key: metadata_msg.get(key, '') for key in METADATA_KEYS}
+
+ # Optional Description field in body (Metadata spec >=2.1)
+ if not info['Description'] and metadata_ver.startswith('2'):
+ info['Description'] = metadata_msg.get_payload().strip()
+
return info
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/__init__.py b/deluge/plugins/AutoAdd/deluge_autoadd/__init__.py
index a409cfcce..5f5e76616 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/__init__.py
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -22,7 +19,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class Gtk3UIPlugin(PluginInitBase):
@@ -30,7 +27,7 @@ class Gtk3UIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(Gtk3UIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -38,4 +35,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/common.py b/deluge/plugins/AutoAdd/deluge_autoadd/common.py
index 9b4b1e703..6a790cbd5 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/common.py
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/core.py b/deluge/plugins/AutoAdd/deluge_autoadd/core.py
index 9a2260610..07ad53a8e 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/core.py
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@@ -13,8 +12,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
import shutil
@@ -30,7 +27,7 @@ import deluge.configmanager
from deluge._libtorrent import lt
from deluge.common import AUTH_LEVEL_ADMIN, is_magnet
from deluge.core.rpcserver import export
-from deluge.error import AddTorrentError
+from deluge.error import AddTorrentError, InvalidTorrentError
from deluge.event import DelugeEvent
from deluge.plugins.pluginbase import CorePluginBase
@@ -152,7 +149,7 @@ class Core(CorePluginBase):
try:
with open(filename, file_mode) as _file:
filedump = _file.read()
- except IOError as ex:
+ except OSError as ex:
log.warning('Unable to open %s: %s', filename, ex)
raise ex
@@ -161,7 +158,10 @@ class Core(CorePluginBase):
# Get the info to see if any exceptions are raised
if not magnet:
- lt.torrent_info(lt.bdecode(filedump))
+ decoded_torrent = lt.bdecode(filedump)
+ if decoded_torrent is None:
+ raise InvalidTorrentError('Torrent file failed decoding.')
+ lt.torrent_info(decoded_torrent)
return filedump
@@ -169,9 +169,9 @@ class Core(CorePluginBase):
log.debug('Attempting to open %s for splitting magnets.', filename)
magnets = []
try:
- with open(filename, 'r') as _file:
+ with open(filename) as _file:
magnets = list(filter(len, _file.read().splitlines()))
- except IOError as ex:
+ except OSError as ex:
log.warning('Unable to open %s: %s', filename, ex)
if len(magnets) < 2:
@@ -196,7 +196,7 @@ class Core(CorePluginBase):
try:
with open(mname, 'w') as _mfile:
_mfile.write(magnet)
- except IOError as ex:
+ except OSError as ex:
log.warning('Unable to open %s: %s', mname, ex)
return magnets
@@ -271,7 +271,7 @@ class Core(CorePluginBase):
try:
filedump = self.load_torrent(filepath, magnet)
- except (IOError, EOFError) as ex:
+ except (OSError, EOFError, InvalidTorrentError) as ex:
# If torrent is invalid, keep track of it so can try again on the next pass.
# This catches torrent files that may not be fully saved to disk at load time.
log.debug('Torrent is invalid: %s', ex)
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js
index ebed40180..e68fce307 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd.js
@@ -42,22 +42,21 @@ Deluge.ux.preferences.AutoAddPage = Ext.extend(Ext.Panel, {
dataIndex: 'enabled',
tpl: new Ext.XTemplate('{enabled:this.getCheckbox}', {
getCheckbox: function (checked, selected) {
- Deluge.ux.AutoAdd.onClickFunctions[
- selected.id
- ] = function () {
- if (selected.enabled) {
- deluge.client.autoadd.disable_watchdir(
- selected.id
- );
- checked = false;
- } else {
- deluge.client.autoadd.enable_watchdir(
- selected.id
- );
- checked = true;
- }
- autoAdd.updateWatchDirs();
- };
+ Deluge.ux.AutoAdd.onClickFunctions[selected.id] =
+ function () {
+ if (selected.enabled) {
+ deluge.client.autoadd.disable_watchdir(
+ selected.id
+ );
+ checked = false;
+ } else {
+ deluge.client.autoadd.enable_watchdir(
+ selected.id
+ );
+ checked = true;
+ }
+ autoAdd.updateWatchDirs();
+ };
return (
'<input id="enabled-' +
selected.id +
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js
index 797d76585..7ec4448b9 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.js
@@ -90,9 +90,8 @@ Deluge.ux.AutoAdd.AutoAddWindowBase = Ext.extend(Ext.Window, {
options['enabled'] = Ext.getCmp('enabled').getValue();
options['path'] = Ext.getCmp('path').getValue();
- options['download_location'] = Ext.getCmp(
- 'download_location'
- ).getValue();
+ options['download_location'] =
+ Ext.getCmp('download_location').getValue();
options['move_completed_path'] = Ext.getCmp(
'move_completed_path'
).getValue();
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui
index a4cd364e9..f1870f183 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/data/autoadd_options.ui
@@ -150,8 +150,6 @@
<property name="tooltip_text" translatable="yes">If a .torrent file is added to this directory,
it will be added to the session.</property>
<property name="invisible_char">●</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -284,8 +282,6 @@ and it will remain in the same directory.</property>
<property name="can_focus">True</property>
<property name="invisible_char">•</property>
<property name="text" translatable="yes">.added</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -329,8 +325,6 @@ and deleted from the watch folder.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">•</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -445,8 +439,6 @@ also delete the .torrent file used to add it.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -534,8 +526,6 @@ also delete the .torrent file used to add it.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -799,8 +789,6 @@ also delete the .torrent file used to add it.</property>
<object class="GtkSpinButton" id="max_download_speed">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment1</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
@@ -815,8 +803,6 @@ also delete the .torrent file used to add it.</property>
<object class="GtkSpinButton" id="max_upload_speed">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment2</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
@@ -833,8 +819,6 @@ also delete the .torrent file used to add it.</property>
<object class="GtkSpinButton" id="max_connections">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment3</property>
<property name="climb_rate">1</property>
</object>
@@ -850,8 +834,6 @@ also delete the .torrent file used to add it.</property>
<object class="GtkSpinButton" id="max_upload_slots">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment4</property>
<property name="climb_rate">1</property>
</object>
@@ -1063,8 +1045,6 @@ also delete the .torrent file used to add it.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment5</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py b/deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py
index 35531419b..80fb9fcde 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
#
@@ -12,14 +11,12 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
import gi # isort:skip (Required before Gtk import).
-gi.require_version('Gtk', '3.0') # NOQA: E402
+gi.require_version('Gtk', '3.0')
# isort:imports-thirdparty
from gi.repository import Gtk
@@ -41,7 +38,7 @@ class IncompatibleOption(Exception):
pass
-class OptionsDialog(object):
+class OptionsDialog:
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
spin_int_ids = ['max_upload_slots', 'max_connections']
chk_ids = [
@@ -327,7 +324,7 @@ class OptionsDialog(object):
dialogs.ErrorDialog(_('Incompatible Option'), str(ex), self.dialog).run()
def on_error_show(self, result):
- d = dialogs.ErrorDialog(_('Error'), result.value.exception_msg, self.dialog)
+ d = dialogs.ErrorDialog(_('Error'), result.value.message, self.dialog)
result.cleanFailure()
d.run()
diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/webui.py b/deluge/plugins/AutoAdd/deluge_autoadd/webui.py
index 7f36ba659..d328432fe 100644
--- a/deluge/plugins/AutoAdd/deluge_autoadd/webui.py
+++ b/deluge/plugins/AutoAdd/deluge_autoadd/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/AutoAdd/setup.py b/deluge/plugins/AutoAdd/setup.py
index fcd018395..5a01ee9aa 100644
--- a/deluge/plugins/AutoAdd/setup.py
+++ b/deluge/plugins/AutoAdd/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 GazpachoKing <chase.sterling@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/__init__.py b/deluge/plugins/Blocklist/deluge_blocklist/__init__.py
index 96ccc02ae..40ce1d18f 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/__init__.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -17,7 +14,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -25,7 +22,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -33,4 +30,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/common.py b/deluge/plugins/Blocklist/deluge_blocklist/common.py
index a9299cd2e..35b2f87c5 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/common.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,13 +11,10 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from functools import wraps
from sys import exc_info
-import six
from pkg_resources import resource_filename
@@ -47,7 +43,7 @@ def raises_errors_as(error):
return func(self, *args, **kwargs)
except Exception:
(value, tb) = exc_info()[1:]
- six.reraise(error, value, tb)
+ raise error(value).with_traceback(tb) from None
return wrapper
@@ -74,7 +70,7 @@ class BadIP(Exception):
_message = None
def __init__(self, message):
- super(BadIP, self).__init__(message)
+ super().__init__(message)
def __set_message(self, message):
self._message = message
@@ -86,7 +82,7 @@ class BadIP(Exception):
del __get_message, __set_message
-class IP(object):
+class IP:
__slots__ = ('q1', 'q2', 'q3', 'q4', '_long')
def __init__(self, q1, q2, q3, q4):
@@ -109,7 +105,7 @@ class IP(object):
@classmethod
def parse(cls, ip):
try:
- q1, q2, q3, q4 = [int(q) for q in ip.split('.')]
+ q1, q2, q3, q4 = (int(q) for q in ip.split('.'))
except ValueError:
raise BadIP(_('The IP address "%s" is badly formed' % ip))
if q1 < 0 or q2 < 0 or q3 < 0 or q4 < 0:
@@ -169,7 +165,7 @@ class IP(object):
return self.long == other.long
def __repr__(self):
- return '<%s long=%s address="%s">' % (
+ return '<{} long={} address="{}">'.format(
self.__class__.__name__,
self.long,
self.address,
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/core.py b/deluge/plugins/Blocklist/deluge_blocklist/core.py
index a096b8ac9..176576740 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/core.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
@@ -8,14 +7,13 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
import os
import shutil
import time
from datetime import datetime, timedelta
from email.utils import formatdate
+from urllib.parse import urljoin
from twisted.internet import defer, threads
from twisted.internet.task import LoopingCall
@@ -32,12 +30,6 @@ from .common import IP, BadIP
from .detect import UnknownFormatError, create_reader, detect_compression, detect_format
from .readers import ReaderParseError
-try:
- from urllib.parse import urljoin
-except ImportError:
- # PY2 fallback
- from urlparse import urljoin # pylint: disable=ungrouped-imports
-
# TODO: check return values for deferred callbacks
# TODO: review class attributes for redundancy
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js
index 8f1b93c21..3c10b81bc 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js
+++ b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist.js
@@ -55,7 +55,7 @@ Deluge.ux.preferences.BlocklistPage = Ext.extend(Ext.Panel, {
});
this.checkListDays = this.SettingsFset.add({
- fieldLabel: _('Check for new list every:'),
+ fieldLabel: _('Check for new list every (days):'),
labelSeparator: '',
name: 'check_list_days',
value: 4,
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui
index 013d8e70e..8c1f7a7ab 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui
+++ b/deluge/plugins/Blocklist/deluge_blocklist/data/blocklist_pref.ui
@@ -53,8 +53,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -124,8 +122,6 @@
<object class="GtkSpinButton" id="spin_check_days">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment1</property>
</object>
<packing>
@@ -139,7 +135,7 @@
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">Check for new list every:</property>
+ <property name="label" translatable="yes">Check for new list every (days):</property>
<property name="xalign">0</property>
</object>
<packing>
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/decompressers.py b/deluge/plugins/Blocklist/deluge_blocklist/decompressers.py
index 35211b706..cd2ee8cad 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/decompressers.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/decompressers.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -8,8 +7,6 @@
#
# pylint: disable=redefined-builtin
-from __future__ import unicode_literals
-
import bz2
import gzip
import zipfile
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/detect.py b/deluge/plugins/Blocklist/deluge_blocklist/detect.py
index 262d5de4f..43ad305f2 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/detect.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/detect.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from .decompressers import BZipped2, GZipped, Zipped
from .readers import EmuleReader, PeerGuardianReader, SafePeerReader
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/gtkui.py b/deluge/plugins/Blocklist/deluge_blocklist/gtkui.py
index b6e5d5508..e6105cda0 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/gtkui.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,14 +6,12 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from datetime import datetime
import gi # isort:skip (Required before Gtk import).
-gi.require_version('Gtk', '3.0') # NOQA: E402
+gi.require_version('Gtk', '3.0')
# isort:imports-thirdparty
from gi.repository import Gtk
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py b/deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py
index ba410c2cf..b5fb181a2 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/peerguardian.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Steve 'Tarka' Smith (tarka@internode.on.net)
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import gzip
import logging
import socket
@@ -23,14 +20,14 @@ class PGException(Exception):
# Incrementally reads PeerGuardian blocklists v1 and v2.
# See http://wiki.phoenixlabs.org/wiki/P2B_Format
-class PGReader(object):
+class PGReader:
def __init__(self, filename):
log.debug('PGReader loading: %s', filename)
try:
with gzip.open(filename, 'rb') as _file:
self.fd = _file
- except IOError:
+ except OSError:
log.debug('Blocklist: PGReader: Incorrect file type or list is corrupt')
# 4 bytes, should be 0xffffffff
@@ -65,8 +62,5 @@ class PGReader(object):
return (start, end)
- # Python 2 compatibility
- next = __next__
-
def close(self):
self.fd.close()
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/readers.py b/deluge/plugins/Blocklist/deluge_blocklist/readers.py
index 4079e849e..14230ed77 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/readers.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/readers.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import re
@@ -23,7 +20,7 @@ class ReaderParseError(Exception):
pass
-class BaseReader(object):
+class BaseReader:
"""Base reader for blocklist files"""
def __init__(self, _file):
diff --git a/deluge/plugins/Blocklist/deluge_blocklist/webui.py b/deluge/plugins/Blocklist/deluge_blocklist/webui.py
index 3da43c451..b8a0ca244 100644
--- a/deluge/plugins/Blocklist/deluge_blocklist/webui.py
+++ b/deluge/plugins/Blocklist/deluge_blocklist/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Blocklist/setup.py b/deluge/plugins/Blocklist/setup.py
index 54ad002a3..2aa683407 100644
--- a/deluge/plugins/Blocklist/setup.py
+++ b/deluge/plugins/Blocklist/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
diff --git a/deluge/plugins/Execute/deluge_execute/__init__.py b/deluge/plugins/Execute/deluge_execute/__init__.py
index c6d55f4ec..3edfc4b1d 100644
--- a/deluge/plugins/Execute/deluge_execute/__init__.py
+++ b/deluge/plugins/Execute/deluge_execute/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -17,7 +14,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -25,7 +22,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -33,4 +30,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Execute/deluge_execute/common.py b/deluge/plugins/Execute/deluge_execute/common.py
index 4c9db09d5..eb47f1398 100644
--- a/deluge/plugins/Execute/deluge_execute/common.py
+++ b/deluge/plugins/Execute/deluge_execute/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/Execute/deluge_execute/core.py b/deluge/plugins/Execute/deluge_execute/core.py
index f5fa2c2e6..6d33e546d 100644
--- a/deluge/plugins/Execute/deluge_execute/core.py
+++ b/deluge/plugins/Execute/deluge_execute/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import hashlib
import logging
import os
diff --git a/deluge/plugins/Execute/deluge_execute/data/execute_prefs.ui b/deluge/plugins/Execute/deluge_execute/data/execute_prefs.ui
index e2a5cd507..5d6354bc9 100644
--- a/deluge/plugins/Execute/deluge_execute/data/execute_prefs.ui
+++ b/deluge/plugins/Execute/deluge_execute/data/execute_prefs.ui
@@ -71,8 +71,6 @@
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
diff --git a/deluge/plugins/Execute/deluge_execute/gtkui.py b/deluge/plugins/Execute/deluge_execute/gtkui.py
index c0c720089..f56a6defa 100644
--- a/deluge/plugins/Execute/deluge_execute/gtkui.py
+++ b/deluge/plugins/Execute/deluge_execute/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
@@ -7,13 +6,11 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import gi # isort:skip (Required before Gtk import).
-gi.require_version('Gtk', '3.0') # NOQA: E402
+gi.require_version('Gtk', '3.0')
# isort:imports-thirdparty
from gi.repository import Gtk
@@ -41,7 +38,7 @@ EVENT_MAP = {
EVENTS = ['complete', 'added', 'removed']
-class ExecutePreferences(object):
+class ExecutePreferences:
def __init__(self, plugin):
self.plugin = plugin
diff --git a/deluge/plugins/Execute/deluge_execute/webui.py b/deluge/plugins/Execute/deluge_execute/webui.py
index 8327001b8..26a444533 100644
--- a/deluge/plugins/Execute/deluge_execute/webui.py
+++ b/deluge/plugins/Execute/deluge_execute/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Execute/setup.py b/deluge/plugins/Execute/setup.py
index 174d1a356..b65c1bd3d 100644
--- a/deluge/plugins/Execute/setup.py
+++ b/deluge/plugins/Execute/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
diff --git a/deluge/plugins/Extractor/deluge_extractor/__init__.py b/deluge/plugins/Extractor/deluge_extractor/__init__.py
index 6db72b63b..87d1584cd 100644
--- a/deluge/plugins/Extractor/deluge_extractor/__init__.py
+++ b/deluge/plugins/Extractor/deluge_extractor/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -21,7 +18,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -29,7 +26,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -37,4 +34,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Extractor/deluge_extractor/common.py b/deluge/plugins/Extractor/deluge_extractor/common.py
index 4c9db09d5..eb47f1398 100644
--- a/deluge/plugins/Extractor/deluge_extractor/common.py
+++ b/deluge/plugins/Extractor/deluge_extractor/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/Extractor/deluge_extractor/core.py b/deluge/plugins/Extractor/deluge_extractor/core.py
index 8fa5bd3da..23b2a00ac 100644
--- a/deluge/plugins/Extractor/deluge_extractor/core.py
+++ b/deluge/plugins/Extractor/deluge_extractor/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import errno
import logging
import os
@@ -37,14 +34,11 @@ if windows_check():
'C:\\Program Files (x86)\\7-Zip\\7z.exe',
]
- try:
- import winreg
- except ImportError:
- import _winreg as winreg # For Python 2.
+ import winreg
try:
hkey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\7-Zip')
- except WindowsError:
+ except OSError:
pass
else:
win_7z_path = os.path.join(winreg.QueryValueEx(hkey, 'Path')[0], '7z.exe')
diff --git a/deluge/plugins/Extractor/deluge_extractor/data/extractor_prefs.ui b/deluge/plugins/Extractor/deluge_extractor/data/extractor_prefs.ui
index 6f34b4411..9e8070b50 100644
--- a/deluge/plugins/Extractor/deluge_extractor/data/extractor_prefs.ui
+++ b/deluge/plugins/Extractor/deluge_extractor/data/extractor_prefs.ui
@@ -62,8 +62,6 @@
<child>
<object class="GtkEntry" id="entry_path">
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
diff --git a/deluge/plugins/Extractor/deluge_extractor/gtkui.py b/deluge/plugins/Extractor/deluge_extractor/gtkui.py
index 113b33f6d..a754a5f22 100644
--- a/deluge/plugins/Extractor/deluge_extractor/gtkui.py
+++ b/deluge/plugins/Extractor/deluge_extractor/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -11,13 +10,11 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import gi # isort:skip (Required before Gtk import).
-gi.require_version('Gtk', '3.0') # NOQA: E402
+gi.require_version('Gtk', '3.0')
# isort:imports-thirdparty
from gi.repository import Gtk
diff --git a/deluge/plugins/Extractor/deluge_extractor/webui.py b/deluge/plugins/Extractor/deluge_extractor/webui.py
index feb7b4a83..715733cb7 100644
--- a/deluge/plugins/Extractor/deluge_extractor/webui.py
+++ b/deluge/plugins/Extractor/deluge_extractor/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Extractor/setup.py b/deluge/plugins/Extractor/setup.py
index 25ab153b3..09385c608 100644
--- a/deluge/plugins/Extractor/setup.py
+++ b/deluge/plugins/Extractor/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
diff --git a/deluge/plugins/Label/deluge_label/__init__.py b/deluge/plugins/Label/deluge_label/__init__.py
index bc0b0f243..a6c72f82b 100644
--- a/deluge/plugins/Label/deluge_label/__init__.py
+++ b/deluge/plugins/Label/deluge_label/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -21,7 +18,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -29,7 +26,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -37,4 +34,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Label/deluge_label/common.py b/deluge/plugins/Label/deluge_label/common.py
index 4c9db09d5..eb47f1398 100644
--- a/deluge/plugins/Label/deluge_label/common.py
+++ b/deluge/plugins/Label/deluge_label/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/Label/deluge_label/core.py b/deluge/plugins/Label/deluge_label/core.py
index b16156c91..a91275f03 100644
--- a/deluge/plugins/Label/deluge_label/core.py
+++ b/deluge/plugins/Label/deluge_label/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -15,8 +14,6 @@
torrent-label core plugin.
adds a status field for tracker.
"""
-from __future__ import unicode_literals
-
import logging
import re
diff --git a/deluge/plugins/Label/deluge_label/data/label.js b/deluge/plugins/Label/deluge_label/data/label.js
index b84ea6b69..a0327e397 100644
--- a/deluge/plugins/Label/deluge_label/data/label.js
+++ b/deluge/plugins/Label/deluge_label/data/label.js
@@ -148,8 +148,7 @@ Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, {
xtype: 'fieldset',
border: false,
labelWidth: 1,
- style:
- 'margin-bottom: 0px; padding-bottom: 0px;',
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
items: [
{
xtype: 'checkbox',
@@ -218,8 +217,7 @@ Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, {
xtype: 'fieldset',
border: false,
labelWidth: 1,
- style:
- 'margin-bottom: 0px; padding-bottom: 0px;',
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
items: [
{
xtype: 'checkbox',
@@ -260,8 +258,7 @@ Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, {
width: 60,
decimalPrecision: 2,
incrementValue: 0.1,
- style:
- 'position: relative; left: 100px',
+ style: 'position: relative; left: 100px',
disabled: true,
},
{
@@ -285,8 +282,7 @@ Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, {
xtype: 'fieldset',
border: false,
labelWidth: 1,
- style:
- 'margin-bottom: 0px; padding-bottom: 0px;',
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
items: [
{
xtype: 'checkbox',
@@ -339,8 +335,7 @@ Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, {
xtype: 'fieldset',
border: false,
labelWidth: 1,
- style:
- 'margin-bottom: 0px; padding-bottom: 0px;',
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
items: [
{
xtype: 'checkbox',
@@ -408,9 +403,8 @@ Deluge.ux.LabelOptionsWindow = Ext.extend(Ext.Window, {
onOkClick: function () {
var values = this.form.getForm().getFieldValues();
if (values['auto_add_trackers']) {
- values['auto_add_trackers'] = values['auto_add_trackers'].split(
- '\n'
- );
+ values['auto_add_trackers'] =
+ values['auto_add_trackers'].split('\n');
}
deluge.client.label.set_options(this.label, values);
this.hide();
diff --git a/deluge/plugins/Label/deluge_label/data/label_add.ui b/deluge/plugins/Label/deluge_label/data/label_add.ui
index 68f8a72b2..e55067596 100644
--- a/deluge/plugins/Label/deluge_label/data/label_add.ui
+++ b/deluge/plugins/Label/deluge_label/data/label_add.ui
@@ -141,8 +141,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="activates_default">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
diff --git a/deluge/plugins/Label/deluge_label/data/label_options.ui b/deluge/plugins/Label/deluge_label/data/label_options.ui
index c0fca4ff5..d390865b1 100644
--- a/deluge/plugins/Label/deluge_label/data/label_options.ui
+++ b/deluge/plugins/Label/deluge_label/data/label_options.ui
@@ -209,8 +209,6 @@
<object class="GtkSpinButton" id="max_upload_speed">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment1</property>
</object>
<packing>
@@ -239,8 +237,6 @@
<object class="GtkSpinButton" id="max_download_speed">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment2</property>
</object>
<packing>
@@ -310,8 +306,6 @@
<object class="GtkSpinButton" id="max_upload_slots">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment3</property>
<property name="numeric">True</property>
</object>
@@ -342,8 +336,6 @@
<object class="GtkSpinButton" id="max_connections">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment4</property>
<property name="numeric">True</property>
</object>
@@ -483,8 +475,6 @@
<object class="GtkSpinButton" id="stop_ratio">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment5</property>
<property name="digits">2</property>
</object>
@@ -599,8 +589,6 @@
<child>
<object class="GtkEntry" id="move_completed_path_entry">
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
diff --git a/deluge/plugins/Label/deluge_label/gtkui/__init__.py b/deluge/plugins/Label/deluge_label/gtkui/__init__.py
index eeaeadcc8..617071628 100644
--- a/deluge/plugins/Label/deluge_label/gtkui/__init__.py
+++ b/deluge/plugins/Label/deluge_label/gtkui/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge import component # for systray
diff --git a/deluge/plugins/Label/deluge_label/gtkui/label_config.py b/deluge/plugins/Label/deluge_label/gtkui/label_config.py
index b1bf56de6..26c827e9a 100644
--- a/deluge/plugins/Label/deluge_label/gtkui/label_config.py
+++ b/deluge/plugins/Label/deluge_label/gtkui/label_config.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from gi.repository.Gtk import Builder
@@ -20,7 +17,7 @@ from ..common import get_resource
log = logging.getLogger(__name__)
-class LabelConfig(object):
+class LabelConfig:
"""
there used to be some options here...
"""
diff --git a/deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py b/deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py
index fe0e86bda..da1830269 100644
--- a/deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py
+++ b/deluge/plugins/Label/deluge_label/gtkui/sidebar_menu.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
@@ -8,13 +7,11 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import gi # isort:skip (Required before Gtk import).
-gi.require_version('Gtk', '3.0') # NOQA: E402
+gi.require_version('Gtk', '3.0')
# isort:imports-thirdparty
from gi.repository import Gtk
@@ -32,7 +29,7 @@ NO_LABEL = 'No Label'
# menu
-class LabelSidebarMenu(object):
+class LabelSidebarMenu:
def __init__(self):
self.treeview = component.get('FilterTreeView')
@@ -107,7 +104,7 @@ class LabelSidebarMenu(object):
# dialogs:
-class AddDialog(object):
+class AddDialog:
def __init__(self):
pass
@@ -129,7 +126,7 @@ class AddDialog(object):
self.dialog.destroy()
-class OptionsDialog(object):
+class OptionsDialog:
spin_ids = ['max_download_speed', 'max_upload_speed', 'stop_ratio']
spin_int_ids = ['max_upload_slots', 'max_connections']
chk_ids = [
@@ -174,7 +171,7 @@ class OptionsDialog(object):
self.builder.connect_signals(self)
# Show the label name in the header label
self.builder.get_object('label_header').set_markup(
- '<b>%s:</b> %s' % (_('Label Options'), self.label)
+ '<b>{}:</b> {}'.format(_('Label Options'), self.label)
)
for chk_id, group in self.sensitive_groups:
diff --git a/deluge/plugins/Label/deluge_label/gtkui/submenu.py b/deluge/plugins/Label/deluge_label/gtkui/submenu.py
index c5f80e70c..ba9324b3d 100644
--- a/deluge/plugins/Label/deluge_label/gtkui/submenu.py
+++ b/deluge/plugins/Label/deluge_label/gtkui/submenu.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -8,8 +7,6 @@
#
-from __future__ import unicode_literals
-
import logging
from gi.repository.Gtk import Menu, MenuItem
diff --git a/deluge/plugins/Label/deluge_label/test.py b/deluge/plugins/Label/deluge_label/test.py
index 5c9ffcd00..739bae429 100644
--- a/deluge/plugins/Label/deluge_label/test.py
+++ b/deluge/plugins/Label/deluge_label/test.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# -*- coding: utf-8 -*-
#
@@ -10,8 +9,6 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
from deluge.ui.client import sclient
sclient.set_core_uri()
diff --git a/deluge/plugins/Label/deluge_label/webui.py b/deluge/plugins/Label/deluge_label/webui.py
index 58c38e941..9ccfa92b5 100644
--- a/deluge/plugins/Label/deluge_label/webui.py
+++ b/deluge/plugins/Label/deluge_label/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Label/setup.py b/deluge/plugins/Label/setup.py
index 567335be2..f8f2c5d3a 100644
--- a/deluge/plugins/Label/setup.py
+++ b/deluge/plugins/Label/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
diff --git a/deluge/plugins/Notifications/deluge_notifications/__init__.py b/deluge/plugins/Notifications/deluge_notifications/__init__.py
index 810e284df..d52b48de3 100644
--- a/deluge/plugins/Notifications/deluge_notifications/__init__.py
+++ b/deluge/plugins/Notifications/deluge_notifications/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Pedro Algarvio <pedro@algarvio.me>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -22,7 +19,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -30,7 +27,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -38,4 +35,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Notifications/deluge_notifications/common.py b/deluge/plugins/Notifications/deluge_notifications/common.py
index 6966122ca..9993f5ce4 100644
--- a/deluge/plugins/Notifications/deluge_notifications/common.py
+++ b/deluge/plugins/Notifications/deluge_notifications/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Pedro Algarvio <pedro@algarvio.me>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os.path
@@ -30,7 +27,7 @@ def get_resource(filename):
return resource_filename(__package__, os.path.join('data', filename))
-class CustomNotifications(object):
+class CustomNotifications:
def __init__(self, plugin_name=None):
self.custom_notifications = {'email': {}, 'popup': {}, 'blink': {}, 'sound': {}}
diff --git a/deluge/plugins/Notifications/deluge_notifications/core.py b/deluge/plugins/Notifications/deluge_notifications/core.py
index 9eede4576..aa200f9bd 100644
--- a/deluge/plugins/Notifications/deluge_notifications/core.py
+++ b/deluge/plugins/Notifications/deluge_notifications/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Pedro Algarvio <pedro@algarvio.me>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import smtplib
from email.utils import formatdate
@@ -119,7 +116,6 @@ Date: %(date)s
message = '\r\n'.join((headers + message).splitlines())
try:
- # Python 2.6
server = smtplib.SMTP(
self.config['smtp_host'], self.config['smtp_port'], timeout=60
)
@@ -152,7 +148,7 @@ Date: %(date)s
try:
try:
- server.sendmail(self.config['smtp_from'], to_addrs, message)
+ server.sendmail(self.config['smtp_from'], to_addrs, message.encode())
except smtplib.SMTPException as ex:
err_msg = (
_('There was an error sending the notification email: %s') % ex
diff --git a/deluge/plugins/Notifications/deluge_notifications/data/config.ui b/deluge/plugins/Notifications/deluge_notifications/data/config.ui
index c16b37acd..399cc9e1c 100644
--- a/deluge/plugins/Notifications/deluge_notifications/data/config.ui
+++ b/deluge/plugins/Notifications/deluge_notifications/data/config.ui
@@ -187,8 +187,6 @@
<object class="GtkEntry" id="smtp_host">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -217,8 +215,6 @@
<property name="can_focus">True</property>
<property name="max_length">5</property>
<property name="width_chars">5</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment1</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
@@ -246,8 +242,6 @@
<object class="GtkEntry" id="smtp_user">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -273,8 +267,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -427,8 +419,6 @@
<object class="GtkEntry" id="smtp_from">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
diff --git a/deluge/plugins/Notifications/deluge_notifications/gtkui.py b/deluge/plugins/Notifications/deluge_notifications/gtkui.py
index cb26901a7..4dc5ff8d1 100644
--- a/deluge/plugins/Notifications/deluge_notifications/gtkui.py
+++ b/deluge/plugins/Notifications/deluge_notifications/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Pedro Algarvio <pedro@algarvio.me>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from os.path import basename
diff --git a/deluge/plugins/Notifications/deluge_notifications/test.py b/deluge/plugins/Notifications/deluge_notifications/test.py
index 2e6f9755b..013cdbfe1 100644
--- a/deluge/plugins/Notifications/deluge_notifications/test.py
+++ b/deluge/plugins/Notifications/deluge_notifications/test.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# vim: sw=4 ts=4 fenc=utf-8 et
# ==============================================================================
# Copyright © 2009-2010 UfSoft.org - Pedro Algarvio <pedro@algarvio.me>
@@ -6,8 +5,6 @@
# License: BSD - Please view the LICENSE file for additional information.
# ==============================================================================
-from __future__ import unicode_literals
-
import logging
from twisted.internet import task
@@ -70,14 +67,14 @@ class TestEmailNotifications(component.Component):
def custom_email_message_provider(self, *evt_args, **evt_kwargs):
log.debug('Running custom email message provider: %s %s', evt_args, evt_kwargs)
- subject = '%s Email Subject: %s' % (self.events[0].__class__.__name__, self.n)
- message = '%s Email Message: %s' % (self.events[0].__class__.__name__, self.n)
+ subject = f'{self.events[0].__class__.__name__} Email Subject: {self.n}'
+ message = f'{self.events[0].__class__.__name__} Email Message: {self.n}'
return subject, message
def custom_popup_message_provider(self, *evt_args, **evt_kwargs):
log.debug('Running custom popup message provider: %s %s', evt_args, evt_kwargs)
- title = '%s Popup Title: %s' % (self.events[0].__class__.__name__, self.n)
- message = '%s Popup Message: %s' % (self.events[0].__class__.__name__, self.n)
+ title = f'{self.events[0].__class__.__name__} Popup Title: {self.n}'
+ message = f'{self.events[0].__class__.__name__} Popup Message: {self.n}'
return title, message
def custom_blink_message_provider(self, *evt_args, **evt_kwargs):
diff --git a/deluge/plugins/Notifications/deluge_notifications/webui.py b/deluge/plugins/Notifications/deluge_notifications/webui.py
index d3529c4f9..ad090f5c1 100644
--- a/deluge/plugins/Notifications/deluge_notifications/webui.py
+++ b/deluge/plugins/Notifications/deluge_notifications/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Pedro Algarvio <pedro@algarvio.me>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Notifications/setup.py b/deluge/plugins/Notifications/setup.py
index 2b2f5aebc..3d8742392 100755
--- a/deluge/plugins/Notifications/setup.py
+++ b/deluge/plugins/Notifications/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Pedro Algarvio <pedro@algarvio.me>
#
diff --git a/deluge/plugins/Scheduler/deluge_scheduler/__init__.py b/deluge/plugins/Scheduler/deluge_scheduler/__init__.py
index 6db72b63b..87d1584cd 100644
--- a/deluge/plugins/Scheduler/deluge_scheduler/__init__.py
+++ b/deluge/plugins/Scheduler/deluge_scheduler/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -21,7 +18,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -29,7 +26,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -37,4 +34,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Scheduler/deluge_scheduler/common.py b/deluge/plugins/Scheduler/deluge_scheduler/common.py
index 4c9db09d5..eb47f1398 100644
--- a/deluge/plugins/Scheduler/deluge_scheduler/common.py
+++ b/deluge/plugins/Scheduler/deluge_scheduler/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/Scheduler/deluge_scheduler/core.py b/deluge/plugins/Scheduler/deluge_scheduler/core.py
index 388e4f0f6..10798ba42 100644
--- a/deluge/plugins/Scheduler/deluge_scheduler/core.py
+++ b/deluge/plugins/Scheduler/deluge_scheduler/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import time
diff --git a/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py b/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py
index 12f5fb63c..16222c835 100644
--- a/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py
+++ b/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
from gi.repository import Gdk, Gtk
@@ -30,7 +27,7 @@ DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
class SchedulerSelectWidget(Gtk.DrawingArea):
def __init__(self, hover):
- super(SchedulerSelectWidget, self).__init__()
+ super().__init__()
self.set_events(
Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.BUTTON_RELEASE_MASK
diff --git a/deluge/plugins/Scheduler/deluge_scheduler/webui.py b/deluge/plugins/Scheduler/deluge_scheduler/webui.py
index 518eaa6aa..4f5418b88 100644
--- a/deluge/plugins/Scheduler/deluge_scheduler/webui.py
+++ b/deluge/plugins/Scheduler/deluge_scheduler/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -10,8 +9,6 @@
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Scheduler/setup.py b/deluge/plugins/Scheduler/setup.py
index 71b69e9f9..3ac181d08 100644
--- a/deluge/plugins/Scheduler/setup.py
+++ b/deluge/plugins/Scheduler/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
diff --git a/deluge/plugins/Stats/deluge_stats/__init__.py b/deluge/plugins/Stats/deluge_stats/__init__.py
index a40379b9a..ca7b0bb83 100644
--- a/deluge/plugins/Stats/deluge_stats/__init__.py
+++ b/deluge/plugins/Stats/deluge_stats/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -21,7 +18,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -29,7 +26,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -37,4 +34,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Stats/deluge_stats/common.py b/deluge/plugins/Stats/deluge_stats/common.py
index 4c9db09d5..eb47f1398 100644
--- a/deluge/plugins/Stats/deluge_stats/common.py
+++ b/deluge/plugins/Stats/deluge_stats/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/Stats/deluge_stats/core.py b/deluge/plugins/Stats/deluge_stats/core.py
index 5f38b69b4..1be51e659 100644
--- a/deluge/plugins/Stats/deluge_stats/core.py
+++ b/deluge/plugins/Stats/deluge_stats/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
@@ -10,8 +9,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
import time
diff --git a/deluge/plugins/Stats/deluge_stats/graph.py b/deluge/plugins/Stats/deluge_stats/graph.py
index bc18913b3..0d3220fc1 100644
--- a/deluge/plugins/Stats/deluge_stats/graph.py
+++ b/deluge/plugins/Stats/deluge_stats/graph.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
# Copyright (C) 2008 Damien Churchill <damoxc@gmail.com>
@@ -14,15 +13,13 @@
port of old plugin by markybob.
"""
-from __future__ import division, unicode_literals
-
import logging
import math
import time
import gi
-gi.require_foreign('cairo') # NOQA: E402
+gi.require_foreign('cairo')
import cairo # isort:skip (gi checks required before import).
@@ -62,7 +59,7 @@ def change_opacity(color, opactiy):
return tuple(color)
-class Graph(object):
+class Graph:
def __init__(self):
self.width = 100
self.height = 100
@@ -178,7 +175,7 @@ class Graph(object):
te = self.ctx.text_extents(text)
return math.ceil(te[4] - te[0])
- y_tick_width = max((space_required(text) for text in y_tick_text))
+ y_tick_width = max(space_required(text) for text in y_tick_text)
top = font_extents[2] / 2
# bounds(left, top, right, bottom)
diff --git a/deluge/plugins/Stats/deluge_stats/gtkui.py b/deluge/plugins/Stats/deluge_stats/gtkui.py
index 75e30150e..c088060dc 100644
--- a/deluge/plugins/Stats/deluge_stats/gtkui.py
+++ b/deluge/plugins/Stats/deluge_stats/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
from gi.repository import Gtk
@@ -85,7 +82,7 @@ def text_to_rgba(color):
class GraphsTab(Tab):
def __init__(self, colors):
- super(GraphsTab, self).__init__()
+ super().__init__()
builder = Gtk.Builder()
builder.add_from_file(get_resource('tabs.ui'))
@@ -270,7 +267,7 @@ class GtkUI(Gtk3PluginBase):
for graph, colors in self.config['colors'].items():
gtkconf[graph] = {}
for value, color in colors.items():
- color_btn = self.builder.get_object('%s_%s_color' % (graph, value))
+ color_btn = self.builder.get_object(f'{graph}_{value}_color')
try:
gtkconf[graph][value] = color_btn.get_color().to_string()
except Exception:
@@ -285,7 +282,7 @@ class GtkUI(Gtk3PluginBase):
for graph, colors in self.config['colors'].items():
for value, color in colors.items():
try:
- color_btn = self.builder.get_object('%s_%s_color' % (graph, value))
+ color_btn = self.builder.get_object(f'{graph}_{value}_color')
color_btn.set_rgba(text_to_rgba(color))
except Exception as ex:
log.debug('Unable to set %s %s %s: %s', graph, value, color, ex)
diff --git a/deluge/plugins/Stats/deluge_stats/tests/test_stats.py b/deluge/plugins/Stats/deluge_stats/tests/test_stats.py
index ab2456877..9c66ee107 100644
--- a/deluge/plugins/Stats/deluge_stats/tests/test_stats.py
+++ b/deluge/plugins/Stats/deluge_stats/tests/test_stats.py
@@ -1,19 +1,14 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import pytest
+import pytest_twisted
from twisted.internet import defer
-from twisted.trial import unittest
import deluge.component as component
from deluge.common import fsize, fspeed
-from deluge.tests import common as tests_common
-from deluge.tests.basetest import BaseTestCase
from deluge.ui.client import client
@@ -26,42 +21,42 @@ def print_totals(totals):
print('down:', fsize(totals['total_download'] - totals['total_payload_download']))
-class StatsTestCase(BaseTestCase):
- def set_up(self):
+@pytest.mark.usefixtures('component')
+class TestStatsPlugin:
+ @pytest_twisted.async_yield_fixture(autouse=True)
+ async def set_up(self):
defer.setDebugging(True)
- tests_common.set_tmp_config_dir()
client.start_standalone()
client.core.enable_plugin('Stats')
- return component.start()
-
- def tear_down(self):
+ await component.start()
+ yield
client.stop_standalone()
- return component.shutdown()
+ await component.shutdown()
@defer.inlineCallbacks
def test_client_totals(self):
plugins = yield client.core.get_available_plugins()
if 'Stats' not in plugins:
- raise unittest.SkipTest('WebUi plugin not available for testing')
+ pytest.skip('Stats plugin not available for testing')
totals = yield client.stats.get_totals()
- self.assertEqual(totals['total_upload'], 0)
- self.assertEqual(totals['total_payload_upload'], 0)
- self.assertEqual(totals['total_payload_download'], 0)
- self.assertEqual(totals['total_download'], 0)
+ assert totals['total_upload'] == 0
+ assert totals['total_payload_upload'] == 0
+ assert totals['total_payload_download'] == 0
+ assert totals['total_download'] == 0
# print_totals(totals)
@defer.inlineCallbacks
def test_session_totals(self):
plugins = yield client.core.get_available_plugins()
if 'Stats' not in plugins:
- raise unittest.SkipTest('WebUi plugin not available for testing')
+ pytest.skip('Stats plugin not available for testing')
totals = yield client.stats.get_session_totals()
- self.assertEqual(totals['total_upload'], 0)
- self.assertEqual(totals['total_payload_upload'], 0)
- self.assertEqual(totals['total_payload_download'], 0)
- self.assertEqual(totals['total_download'], 0)
+ assert totals['total_upload'] == 0
+ assert totals['total_payload_upload'] == 0
+ assert totals['total_payload_download'] == 0
+ assert totals['total_download'] == 0
# print_totals(totals)
@pytest.mark.gtkui
@@ -90,7 +85,7 @@ class StatsTestCase(BaseTestCase):
TorrentDetails()
Preferences()
- class FakeFile(object):
+ class FakeFile:
def __init__(self):
self.data = []
diff --git a/deluge/plugins/Stats/deluge_stats/webui.py b/deluge/plugins/Stats/deluge_stats/webui.py
index 4c11260e5..f38daeb64 100644
--- a/deluge/plugins/Stats/deluge_stats/webui.py
+++ b/deluge/plugins/Stats/deluge_stats/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Stats/setup.py b/deluge/plugins/Stats/setup.py
index 174c652a9..0f3e0695b 100644
--- a/deluge/plugins/Stats/setup.py
+++ b/deluge/plugins/Stats/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Ian Martin <ianmartin@cantab.net>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
diff --git a/deluge/plugins/Toggle/deluge_toggle/__init__.py b/deluge/plugins/Toggle/deluge_toggle/__init__.py
index e63e4aa4c..b0332ee9c 100644
--- a/deluge/plugins/Toggle/deluge_toggle/__init__.py
+++ b/deluge/plugins/Toggle/deluge_toggle/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -22,7 +19,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -30,7 +27,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -38,4 +35,4 @@ class WebUIPlugin(PluginInitBase):
from .webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/Toggle/deluge_toggle/common.py b/deluge/plugins/Toggle/deluge_toggle/common.py
index 4c9db09d5..eb47f1398 100644
--- a/deluge/plugins/Toggle/deluge_toggle/common.py
+++ b/deluge/plugins/Toggle/deluge_toggle/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/Toggle/deluge_toggle/core.py b/deluge/plugins/Toggle/deluge_toggle/core.py
index dad52ce61..ab4581b47 100644
--- a/deluge/plugins/Toggle/deluge_toggle/core.py
+++ b/deluge/plugins/Toggle/deluge_toggle/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
diff --git a/deluge/plugins/Toggle/deluge_toggle/gtkui.py b/deluge/plugins/Toggle/deluge_toggle/gtkui.py
index c54bca46f..bfb90de1b 100644
--- a/deluge/plugins/Toggle/deluge_toggle/gtkui.py
+++ b/deluge/plugins/Toggle/deluge_toggle/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
diff --git a/deluge/plugins/Toggle/deluge_toggle/webui.py b/deluge/plugins/Toggle/deluge_toggle/webui.py
index 8f0fc8c99..d16d29fff 100644
--- a/deluge/plugins/Toggle/deluge_toggle/webui.py
+++ b/deluge/plugins/Toggle/deluge_toggle/webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.plugins.pluginbase import WebPluginBase
diff --git a/deluge/plugins/Toggle/setup.py b/deluge/plugins/Toggle/setup.py
index acc6e6c7d..dadd32ebc 100644
--- a/deluge/plugins/Toggle/setup.py
+++ b/deluge/plugins/Toggle/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
diff --git a/deluge/plugins/WebUi/deluge_webui/__init__.py b/deluge/plugins/WebUi/deluge_webui/__init__.py
index a3d29805a..ba978b224 100644
--- a/deluge/plugins/WebUi/deluge_webui/__init__.py
+++ b/deluge/plugins/WebUi/deluge_webui/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.plugins.init import PluginInitBase
@@ -21,7 +18,7 @@ class CorePlugin(PluginInitBase):
from .core import Core as _pluginCls
self._plugin_cls = _pluginCls
- super(CorePlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
@@ -29,7 +26,7 @@ class GtkUIPlugin(PluginInitBase):
from .gtkui import GtkUI as _pluginCls
self._plugin_cls = _pluginCls
- super(GtkUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
@@ -37,4 +34,4 @@ class WebUIPlugin(PluginInitBase):
from webui import WebUI as _pluginCls
self._plugin_cls = _pluginCls
- super(WebUIPlugin, self).__init__(plugin_name)
+ super().__init__(plugin_name)
diff --git a/deluge/plugins/WebUi/deluge_webui/common.py b/deluge/plugins/WebUi/deluge_webui/common.py
index 4c9db09d5..eb47f1398 100644
--- a/deluge/plugins/WebUi/deluge_webui/common.py
+++ b/deluge/plugins/WebUi/deluge_webui/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -12,8 +11,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os.path
from pkg_resources import resource_filename
diff --git a/deluge/plugins/WebUi/deluge_webui/core.py b/deluge/plugins/WebUi/deluge_webui/core.py
index cc3330fc0..f18203e90 100644
--- a/deluge/plugins/WebUi/deluge_webui/core.py
+++ b/deluge/plugins/WebUi/deluge_webui/core.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from twisted.internet import defer
diff --git a/deluge/plugins/WebUi/deluge_webui/data/config.ui b/deluge/plugins/WebUi/deluge_webui/data/config.ui
index 18647a415..c58edd0cd 100644
--- a/deluge/plugins/WebUi/deluge_webui/data/config.ui
+++ b/deluge/plugins/WebUi/deluge_webui/data/config.ui
@@ -86,8 +86,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment1</property>
<property name="numeric">True</property>
</object>
diff --git a/deluge/plugins/WebUi/deluge_webui/gtkui.py b/deluge/plugins/WebUi/deluge_webui/gtkui.py
index ca3a16ea4..3d19417dc 100644
--- a/deluge/plugins/WebUi/deluge_webui/gtkui.py
+++ b/deluge/plugins/WebUi/deluge_webui/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
@@ -11,8 +10,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from gi.repository import Gtk
diff --git a/deluge/plugins/WebUi/deluge_webui/tests/test_plugin_webui.py b/deluge/plugins/WebUi/deluge_webui/tests/test_plugin_webui.py
index 56e1cc023..1badedca0 100644
--- a/deluge/plugins/WebUi/deluge_webui/tests/test_plugin_webui.py
+++ b/deluge/plugins/WebUi/deluge_webui/tests/test_plugin_webui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -6,44 +5,44 @@
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-
-from __future__ import unicode_literals
-
-from twisted.trial import unittest
+import pytest
+import pytest_twisted
import deluge.component as component
from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer
from deluge.tests import common
-from deluge.tests.basetest import BaseTestCase
common.disable_new_release_check()
-class WebUIPluginTestCase(BaseTestCase):
- def set_up(self):
- common.set_tmp_config_dir()
+@pytest.mark.usefixtures('component')
+class TestWebUIPlugin:
+ @pytest_twisted.async_yield_fixture(autouse=True)
+ async def set_up(self, request):
+ self = request.instance
self.rpcserver = RPCServer(listen=False)
self.core = Core()
- return component.start()
+ await component.start()
+
+ yield
- def tear_down(self):
def on_shutdown(result):
del self.rpcserver
del self.core
- return component.shutdown().addCallback(on_shutdown)
+ await component.shutdown().addCallback(on_shutdown)
def test_enable_webui(self):
if 'WebUi' not in self.core.get_available_plugins():
- raise unittest.SkipTest('WebUi plugin not available for testing')
+ pytest.skip('WebUi plugin not available for testing')
d = self.core.enable_plugin('WebUi')
def result_cb(result):
if 'WebUi' not in self.core.get_enabled_plugins():
self.fail('Failed to enable WebUi plugin')
- self.assertTrue(result)
+ assert result
d.addBoth(result_cb)
return d
diff --git a/deluge/plugins/WebUi/setup.py b/deluge/plugins/WebUi/setup.py
index 861a05a50..5f2184cc9 100644
--- a/deluge/plugins/WebUi/setup.py
+++ b/deluge/plugins/WebUi/setup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
diff --git a/deluge/plugins/init.py b/deluge/plugins/init.py
index addeae9f3..56b31977d 100644
--- a/deluge/plugins/init.py
+++ b/deluge/plugins/init.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -10,14 +9,12 @@
"""
This base class is used in plugin's __init__ for the plugin entry points.
"""
-from __future__ import unicode_literals
-
import logging
log = logging.getLogger(__name__)
-class PluginInitBase(object):
+class PluginInitBase:
_plugin_cls = None
def __init__(self, plugin_name):
diff --git a/deluge/plugins/pluginbase.py b/deluge/plugins/pluginbase.py
index e80199df1..5dda2f077 100644
--- a/deluge/plugins/pluginbase.py
+++ b/deluge/plugins/pluginbase.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2010 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
@@ -21,7 +18,7 @@ class PluginBase(component.Component):
update_interval = 1
def __init__(self, name):
- super(PluginBase, self).__init__(name, self.update_interval)
+ super().__init__(name, self.update_interval)
def enable(self):
raise NotImplementedError('Need to define an enable method!')
@@ -32,7 +29,7 @@ class PluginBase(component.Component):
class CorePluginBase(PluginBase):
def __init__(self, plugin_name):
- super(CorePluginBase, self).__init__('CorePlugin.' + plugin_name)
+ super().__init__('CorePlugin.' + plugin_name)
# Register RPC methods
component.get('RPCServer').register_object(self, plugin_name.lower())
log.debug('CorePlugin initialized..')
@@ -41,22 +38,22 @@ class CorePluginBase(PluginBase):
component.get('RPCServer').deregister_object(self)
def enable(self):
- super(CorePluginBase, self).enable()
+ super().enable()
def disable(self):
- super(CorePluginBase, self).disable()
+ super().disable()
class Gtk3PluginBase(PluginBase):
def __init__(self, plugin_name):
- super(Gtk3PluginBase, self).__init__('Gtk3Plugin.' + plugin_name)
+ super().__init__('Gtk3Plugin.' + plugin_name)
log.debug('Gtk3Plugin initialized..')
def enable(self):
- super(Gtk3PluginBase, self).enable()
+ super().enable()
def disable(self):
- super(Gtk3PluginBase, self).disable()
+ super().disable()
class WebPluginBase(PluginBase):
@@ -68,7 +65,7 @@ class WebPluginBase(PluginBase):
debug_stylesheets = []
def __init__(self, plugin_name):
- super(WebPluginBase, self).__init__('WebPlugin.' + plugin_name)
+ super().__init__('WebPlugin.' + plugin_name)
# Register JSON rpc methods
component.get('JSON').register_object(self, plugin_name.lower())
diff --git a/deluge/scripts/create_plugin.py b/deluge/scripts/create_plugin.py
index 44513ed45..266747b94 100644
--- a/deluge/scripts/create_plugin.py
+++ b/deluge/scripts/create_plugin.py
@@ -7,8 +7,6 @@ python create_plugin.py --name MyPlugin2 --basepath . --author-name "Your Name"
"""
-from __future__ import print_function, unicode_literals
-
import os
import sys
from argparse import ArgumentParser
@@ -115,9 +113,13 @@ def create_plugin():
# add an input parameter for this?
print('building dev-link..')
- write_file(plugin_base, 'create_dev_link.sh', CREATE_DEV_LINK)
- dev_link_path = os.path.join(plugin_base, 'create_dev_link.sh')
- os.system('chmod +x %s' % dev_link_path) # lazy..
+ if deluge.common.windows_check():
+ write_file(plugin_base, 'create_dev_link.bat', CREATE_DEV_LINK_WIN)
+ dev_link_path = os.path.join(plugin_base, 'create_dev_link.bat')
+ else:
+ write_file(plugin_base, 'create_dev_link.sh', CREATE_DEV_LINK_NIX)
+ dev_link_path = os.path.join(plugin_base, 'create_dev_link.sh')
+ os.system('chmod +x %s' % dev_link_path) # lazy..
os.system(dev_link_path)
@@ -374,7 +376,7 @@ GPL = """# -*- coding: utf-8 -*-
# the OpenSSL library. See LICENSE for more details.
"""
-CREATE_DEV_LINK = """#!/bin/bash
+CREATE_DEV_LINK_NIX = """#!/bin/bash
BASEDIR=$(cd `dirname $0` && pwd)
CONFIG_DIR=$( test -z $1 && echo "%(configdir)s" || echo "$1")
[ -d "$CONFIG_DIR/plugins" ] || echo "Config dir \"$CONFIG_DIR\" is either not a directory \
@@ -388,4 +390,27 @@ cp $BASEDIR/temp/*.egg-link $CONFIG_DIR/plugins
rm -fr $BASEDIR/temp
"""
+CREATE_DEV_LINK_WIN = """@echo off
+set BASEDIR=%%~dp0
+set BASEDIR=%%BASEDIR:~0,-1%%
+if [%%1]==[] (
+ set CONFIG_DIR=%(configdir)s
+) else (
+ set CONFIG_DIR=%%1
+)
+if not exist %%CONFIG_DIR%%\\plugins (
+ echo Config dir %%CONFIG_DIR%% is either not a directory \
+or is not a proper deluge config directory. Exiting
+ exit /b 1
+)
+cd %%BASEDIR%%
+if not exist %%BASEDIR%%\\temp (
+ md %%BASEDIR%%\\temp
+)
+set PYTHONPATH=%%BASEDIR%%/temp
+%(python_path)s setup.py build develop --install-dir %%BASEDIR%%\\temp
+copy "%%BASEDIR%%\\temp\\*.egg-link" "%%CONFIG_DIR%%\\plugins"
+rd /s /q %%BASEDIR%%\\temp
+"""
+
create_plugin()
diff --git a/deluge/scripts/deluge_remote.py b/deluge/scripts/deluge_remote.py
index bacc4f88d..d983e5398 100644
--- a/deluge/scripts/deluge_remote.py
+++ b/deluge/scripts/deluge_remote.py
@@ -1,5 +1,4 @@
#!/usr/bin/python
-# -*- coding: utf-8 -*-
#
# This software is in the public domain, furnished "as is", without technical
# support, and with no warranty, express or implied, as to its usefulness for
@@ -12,8 +11,6 @@
#
# Authour: Garett Harnish
-from __future__ import unicode_literals
-
import logging
import sys
from optparse import OptionParser
diff --git a/deluge/tests/__init__.py b/deluge/tests/__init__.py
index d3bf10def..7b6afa194 100644
--- a/deluge/tests/__init__.py
+++ b/deluge/tests/__init__.py
@@ -1,7 +1,5 @@
# Increase open file descriptor limit to allow tests to run
# without getting error: what(): epoll: Too many open files
-from __future__ import print_function, unicode_literals
-
from deluge.i18n import setup_translation
try:
diff --git a/deluge/tests/basetest.py b/deluge/tests/basetest.py
deleted file mode 100644
index 11ca18e53..000000000
--- a/deluge/tests/basetest.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
-# the additional special exception to link portions of this program with the OpenSSL library.
-# See LICENSE for more details.
-#
-
-from __future__ import unicode_literals
-
-import warnings
-
-from twisted.internet.defer import maybeDeferred
-from twisted.trial import unittest
-
-import deluge.component as component
-
-
-class BaseTestCase(unittest.TestCase):
- """This is the base class that should be used for all test classes
- that create classes that inherit from deluge.component.Component. It
- ensures that the component registry has been cleaned up when tests
- have finished.
-
- """
-
- def setUp(self): # NOQA: N803
-
- if len(component._ComponentRegistry.components) != 0:
- warnings.warn(
- 'The component._ComponentRegistry.components is not empty on test setup.\n'
- 'This is probably caused by another test that did not clean up after finishing!: %s'
- % component._ComponentRegistry.components
- )
- d = maybeDeferred(self.set_up)
-
- def on_setup_error(error):
- warnings.warn('Error caught in test setup!\n%s' % error.getTraceback())
- self.fail()
-
- return d.addErrback(on_setup_error)
-
- def tearDown(self): # NOQA: N803
- d = maybeDeferred(self.tear_down)
-
- def on_teardown_failed(error):
- warnings.warn('Error caught in test teardown!\n%s' % error.getTraceback())
- self.fail()
-
- def on_teardown_complete(result):
- component._ComponentRegistry.components.clear()
- component._ComponentRegistry.dependents.clear()
-
- return d.addCallbacks(on_teardown_complete, on_teardown_failed)
-
- def set_up(self):
- pass
-
- def tear_down(self):
- pass
diff --git a/deluge/tests/common.py b/deluge/tests/common.py
index be33f8c58..b5941568d 100644
--- a/deluge/tests/common.py
+++ b/deluge/tests/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,22 +6,21 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import os
import sys
-import tempfile
import traceback
+import pytest
from twisted.internet import defer, protocol, reactor
from twisted.internet.defer import Deferred
from twisted.internet.error import CannotListenError
-from twisted.trial import unittest
import deluge.configmanager
import deluge.core.preferencesmanager
import deluge.log
+from deluge.common import get_localhost_auth
from deluge.error import DelugeError
+from deluge.ui.client import Client
# This sets log level to critical, so use log.critical() to debug while running unit tests
deluge.log.setup_logger('none')
@@ -32,12 +30,6 @@ def disable_new_release_check():
deluge.core.preferencesmanager.DEFAULT_PREFS['new_release_check'] = False
-def set_tmp_config_dir():
- config_directory = tempfile.mkdtemp()
- deluge.configmanager.set_config_dir(config_directory)
- return config_directory
-
-
def setup_test_logger(level='info', prefix='deluge'):
deluge.log.setup_logger(level, filename='%s.log' % prefix, twisted_observer=False)
@@ -55,7 +47,7 @@ def todo_test(caller):
filename = os.path.basename(traceback.extract_stack(None, 2)[0][0])
funcname = traceback.extract_stack(None, 2)[0][2]
- raise unittest.SkipTest('TODO: %s:%s' % (filename, funcname))
+ pytest.skip(f'TODO: {filename}:{funcname}')
def add_watchdog(deferred, timeout=0.05, message=None):
@@ -73,7 +65,7 @@ def add_watchdog(deferred, timeout=0.05, message=None):
return watchdog
-class ReactorOverride(object):
+class ReactorOverride:
"""Class used to patch reactor while running unit tests
to avoid starting and stopping the twisted reactor
"""
@@ -97,12 +89,19 @@ class ReactorOverride(object):
class ProcessOutputHandler(protocol.ProcessProtocol):
def __init__(
- self, script, callbacks, logfile=None, print_stdout=True, print_stderr=True
+ self,
+ script,
+ shutdown_func,
+ callbacks,
+ logfile=None,
+ print_stdout=True,
+ print_stderr=True,
):
"""Executes a script and handle the process' output to stdout and stderr.
Args:
script (str): The script to execute.
+ shutdown_func (func): A function which will gracefully stop the called script.
callbacks (list): Callbacks to trigger if the expected output if found.
logfile (str, optional): Filename to wrote the process' output.
print_stderr (bool): Print the process' stderr output to stdout.
@@ -111,6 +110,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
"""
self.callbacks = callbacks
self.script = script
+ self.shutdown_func = shutdown_func
self.log_output = ''
self.stderr_out = ''
self.logfile = logfile
@@ -130,6 +130,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
with open(self.logfile, 'w') as f:
f.write(self.log_output)
+ @defer.inlineCallbacks
def kill(self):
"""Kill the running process.
@@ -142,11 +143,17 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
self.killed = True
self._kill_watchdogs()
self.quit_d = Deferred()
- self.transport.signalProcess('INT')
- return self.quit_d
+ shutdown = self.shutdown_func()
+ shutdown.addTimeout(5, reactor)
+ try:
+ yield shutdown
+ except Exception:
+ self.transport.signalProcess('TERM')
+ result = yield self.quit_d
+ return result
def _kill_watchdogs(self):
- """"Cancel all watchdogs"""
+ """Cancel all watchdogs"""
for w in self.watchdogs:
if not w.called and not w.cancelled:
w.cancel()
@@ -205,7 +212,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
def start_core(
- listen_port=58846,
+ listen_port=58900,
logfile=None,
timeout=10,
timeout_msg=None,
@@ -213,13 +220,14 @@ def start_core(
print_stdout=True,
print_stderr=True,
extra_callbacks=None,
+ config_directory='',
):
"""Start the deluge core as a daemon.
Args:
listen_port (int, optional): The port the daemon listens for client connections.
logfile (str, optional): Logfile name to write the output from the process.
- timeout (int): If none of the callbacks have been triggered before the imeout, the process is killed.
+ timeout (int): If none of the callbacks have been triggered before the timeout, the process is killed.
timeout_msg (str): The message to print when the timeout expires.
custom_script (str): Extra python code to insert into the daemon process script.
print_stderr (bool): If the output from the process' stderr should be printed to stdout.
@@ -234,7 +242,6 @@ def start_core(
or upon timeout expiry. The ProcessOutputHandler is the handler for the deluged process.
"""
- config_directory = set_tmp_config_dir()
daemon_script = """
import sys
import deluge.core.daemon_entry
@@ -254,7 +261,7 @@ except Exception:
import traceback
sys.stderr.write('Exception raised:\\n %%s' %% traceback.format_exc())
""" % {
- 'dir': config_directory,
+ 'dir': config_directory.as_posix(),
'port': listen_port,
'script': custom_script,
}
@@ -289,20 +296,30 @@ except Exception:
if extra_callbacks:
callbacks.extend(extra_callbacks)
+ @defer.inlineCallbacks
+ def shutdown_daemon():
+ username, password = get_localhost_auth()
+ client = Client()
+ yield client.connect(
+ 'localhost', listen_port, username=username, password=password
+ )
+ yield client.daemon.shutdown()
+
process_protocol = start_process(
- daemon_script, callbacks, logfile, print_stdout, print_stderr
+ daemon_script, shutdown_daemon, callbacks, logfile, print_stdout, print_stderr
)
return default_core_cb['deferred'], process_protocol
def start_process(
- script, callbacks, logfile=None, print_stdout=True, print_stderr=True
+ script, shutdown_func, callbacks, logfile=None, print_stdout=True, print_stderr=True
):
"""
Starts an external python process which executes the given script.
Args:
script (str): The content of the script to execute.
+ shutdown_func (func): A function which will gracefully end the called script.
callbacks (list): list of dictionaries specifying callbacks.
logfile (str, optional): Logfile name to write the output from the process.
print_stderr (bool): If the output from the process' stderr should be printed to stdout.
@@ -324,7 +341,12 @@ def start_process(
"""
cwd = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
process_protocol = ProcessOutputHandler(
- script.encode('utf8'), callbacks, logfile, print_stdout, print_stderr
+ script.encode('utf8'),
+ shutdown_func,
+ callbacks,
+ logfile,
+ print_stdout,
+ print_stderr,
)
# Add timeouts to deferreds
diff --git a/deluge/tests/common_web.py b/deluge/tests/common_web.py
index 706eb8d72..8db49d243 100644
--- a/deluge/tests/common_web.py
+++ b/deluge/tests/common_web.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,21 +6,20 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
+import pytest
import deluge.common
-import deluge.component as component
import deluge.ui.web.auth
import deluge.ui.web.server
from deluge import configmanager
+from deluge.conftest import BaseTestCase
from deluge.ui.web.server import DelugeWeb
-from .basetest import BaseTestCase
from .common import ReactorOverride
-from .daemon_base import DaemonBase
-class WebServerTestBase(BaseTestCase, DaemonBase):
+@pytest.mark.usefixtures('daemon', 'component')
+class WebServerTestBase(BaseTestCase):
"""
Base class for tests that need a running webapi
@@ -30,10 +28,7 @@ class WebServerTestBase(BaseTestCase, DaemonBase):
def set_up(self):
self.host_id = None
deluge.ui.web.server.reactor = ReactorOverride()
- d = self.common_set_up()
- d.addCallback(self.start_core)
- d.addCallback(self.start_webapi)
- return d
+ return self.start_webapi(None)
def start_webapi(self, arg):
self.webserver_listen_port = 8999
@@ -50,13 +45,8 @@ class WebServerTestBase(BaseTestCase, DaemonBase):
self.host_id = host[0]
self.deluge_web.start()
- def tear_down(self):
- d = component.shutdown()
- d.addCallback(self.terminate_core)
- return d
-
-class WebServerMockBase(object):
+class WebServerMockBase:
"""
Class with utility functions for mocking with tests using the webserver
diff --git a/deluge/tests/daemon_base.py b/deluge/tests/daemon_base.py
index 7352e0d32..3ae86c4ca 100644
--- a/deluge/tests/daemon_base.py
+++ b/deluge/tests/daemon_base.py
@@ -1,12 +1,9 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import os.path
import pytest
@@ -14,24 +11,13 @@ from twisted.internet import defer
from twisted.internet.error import CannotListenError
import deluge.component as component
-from deluge.common import windows_check
from . import common
-@pytest.mark.usefixtures('get_pytest_basetemp')
-class DaemonBase(object):
- basetemp = None
-
- if windows_check():
- skip = 'windows cant start_core not enough arguments for format string'
-
- @pytest.fixture
- def get_pytest_basetemp(self, request):
- self.basetemp = request.config.option.basetemp
-
+@pytest.mark.usefixtures('config_dir')
+class DaemonBase:
def common_set_up(self):
- common.set_tmp_config_dir()
self.listen_port = 58900
self.core = None
return component.start()
@@ -78,6 +64,7 @@ class DaemonBase(object):
print_stdout=print_stdout,
print_stderr=print_stderr,
extra_callbacks=extra_callbacks,
+ config_directory=self.config_dir,
)
yield d
except CannotListenError as ex:
diff --git a/deluge/tests/data/seo.ico b/deluge/tests/data/seo.ico
deleted file mode 100644
index 841e52871..000000000
--- a/deluge/tests/data/seo.ico
+++ /dev/null
Binary files differ
diff --git a/deluge/tests/data/seo.svg b/deluge/tests/data/seo.svg
new file mode 100644
index 000000000..fc96f74d4
--- /dev/null
+++ b/deluge/tests/data/seo.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95.63 110.42" width="10cm" height="10cm"><defs><style>.cls-1{fill:#ec5728;}.cls-2{fill:#fff;}</style></defs><title>seocom-target</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="95.63 82.81 47.81 110.42 0 82.81 0 27.61 47.81 0 95.63 27.61 95.63 82.81"></polygon><path class="cls-2" d="M47.81,18.64A36.57,36.57,0,1,0,84.38,55.21,36.57,36.57,0,0,0,47.81,18.64Zm0,63.92A27.35,27.35,0,1,1,75.16,55.21,27.35,27.35,0,0,1,47.81,82.56Z"></path><path class="cls-2" d="M47.81,39.46A15.75,15.75,0,1,0,63.56,55.21,15.75,15.75,0,0,0,47.81,39.46Zm0,24.25a8.5,8.5,0,1,1,8.5-8.5A8.51,8.51,0,0,1,47.81,63.71Z"></path></g></g></svg> \ No newline at end of file
diff --git a/deluge/tests/test_alertmanager.py b/deluge/tests/test_alertmanager.py
index f197882cd..5e63864e8 100644
--- a/deluge/tests/test_alertmanager.py
+++ b/deluge/tests/test_alertmanager.py
@@ -1,19 +1,15 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
+from deluge.conftest import BaseTestCase
from deluge.core.core import Core
-from .basetest import BaseTestCase
-
-class AlertManagerTestCase(BaseTestCase):
+class TestAlertManager(BaseTestCase):
def set_up(self):
self.core = Core()
self.core.config.config['lsd'] = False
@@ -28,7 +24,7 @@ class AlertManagerTestCase(BaseTestCase):
return
self.am.register_handler('dummy_alert', handler)
- self.assertEqual(self.am.handlers['dummy_alert'], [handler])
+ assert self.am.handlers['dummy_alert'] == [handler]
def test_deregister_handler(self):
def handler(alert):
@@ -36,4 +32,4 @@ class AlertManagerTestCase(BaseTestCase):
self.am.register_handler('dummy_alert', handler)
self.am.deregister_handler(handler)
- self.assertEqual(self.am.handlers['dummy_alert'], [])
+ assert self.am.handlers['dummy_alert'] == []
diff --git a/deluge/tests/test_authmanager.py b/deluge/tests/test_authmanager.py
index 91e122f73..aa86fdbac 100644
--- a/deluge/tests/test_authmanager.py
+++ b/deluge/tests/test_authmanager.py
@@ -1,20 +1,16 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from deluge.common import get_localhost_auth
+from deluge.conftest import BaseTestCase
from deluge.core.authmanager import AUTH_LEVEL_ADMIN, AuthManager
-from .basetest import BaseTestCase
-
-class AuthManagerTestCase(BaseTestCase):
+class TestAuthManager(BaseTestCase):
def set_up(self):
self.auth = AuthManager()
self.auth.start()
@@ -24,4 +20,4 @@ class AuthManagerTestCase(BaseTestCase):
return component.shutdown()
def test_authorize(self):
- self.assertEqual(self.auth.authorize(*get_localhost_auth()), AUTH_LEVEL_ADMIN)
+ assert self.auth.authorize(*get_localhost_auth()) == AUTH_LEVEL_ADMIN
diff --git a/deluge/tests/test_bencode.py b/deluge/tests/test_bencode.py
index b49c21f83..a4a76818f 100644
--- a/deluge/tests/test_bencode.py
+++ b/deluge/tests/test_bencode.py
@@ -1,19 +1,17 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-from twisted.trial import unittest
+import pytest
from deluge import bencode
from . import common
-class BencodeTestCase(unittest.TestCase):
+class TestBencode:
def test_bencode_unicode_metainfo(self):
filename = common.get_test_data_file('test.torrent')
with open(filename, 'rb') as _file:
@@ -21,14 +19,14 @@ class BencodeTestCase(unittest.TestCase):
bencode.bencode({b'info': metainfo})
def test_bencode_unicode_value(self):
- self.assertEqual(bencode.bencode(b'abc'), b'3:abc')
- self.assertEqual(bencode.bencode('abc'), b'3:abc')
+ assert bencode.bencode(b'abc') == b'3:abc'
+ assert bencode.bencode('abc') == b'3:abc'
def test_bdecode(self):
- self.assertEqual(bencode.bdecode(b'3:dEf'), b'dEf')
- with self.assertRaises(bencode.BTFailure):
+ assert bencode.bdecode(b'3:dEf') == b'dEf'
+ with pytest.raises(bencode.BTFailure):
bencode.bdecode('dEf')
- with self.assertRaises(bencode.BTFailure):
+ with pytest.raises(bencode.BTFailure):
bencode.bdecode(b'dEf')
- with self.assertRaises(bencode.BTFailure):
+ with pytest.raises(bencode.BTFailure):
bencode.bdecode({'dEf': 123})
diff --git a/deluge/tests/test_client.py b/deluge/tests/test_client.py
index ae1e95a71..5a6727907 100644
--- a/deluge/tests/test_client.py
+++ b/deluge/tests/test_client.py
@@ -1,23 +1,17 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-
-from __future__ import unicode_literals
-
+import pytest
+import pytest_twisted
from twisted.internet import defer
-import deluge.component as component
from deluge import error
from deluge.common import AUTH_LEVEL_NORMAL, get_localhost_auth
from deluge.core.authmanager import AUTH_LEVEL_ADMIN
from deluge.ui.client import Client, DaemonSSLProxy, client
-from .basetest import BaseTestCase
-from .daemon_base import DaemonBase
-
class NoVersionSendingDaemonSSLProxy(DaemonSSLProxy):
def authenticate(self, username, password):
@@ -78,24 +72,13 @@ class NoVersionSendingClient(Client):
self.disconnect_callback()
-class ClientTestCase(BaseTestCase, DaemonBase):
- def set_up(self):
- d = self.common_set_up()
- d.addCallback(self.start_core)
- d.addErrback(self.terminate_core)
- return d
-
- def tear_down(self):
- d = component.shutdown()
- d.addCallback(self.terminate_core)
- return d
-
+@pytest.mark.usefixtures('daemon', 'client')
+class TestClient:
def test_connect_no_credentials(self):
d = client.connect('localhost', self.listen_port, username='', password='')
def on_connect(result):
- self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
- self.addCleanup(client.disconnect)
+ assert client.get_auth_level() == AUTH_LEVEL_ADMIN
return result
d.addCallbacks(on_connect, self.fail)
@@ -108,8 +91,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
)
def on_connect(result):
- self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
- self.addCleanup(client.disconnect)
+ assert client.get_auth_level() == AUTH_LEVEL_ADMIN
return result
d.addCallbacks(on_connect, self.fail)
@@ -122,21 +104,18 @@ class ClientTestCase(BaseTestCase, DaemonBase):
)
def on_failure(failure):
- self.assertEqual(failure.trap(error.BadLoginError), error.BadLoginError)
- self.assertEqual(failure.value.message, 'Password does not match')
- self.addCleanup(client.disconnect)
+ assert failure.trap(error.BadLoginError) == error.BadLoginError
+ assert failure.value.message == 'Password does not match'
d.addCallbacks(self.fail, on_failure)
return d
def test_connect_invalid_user(self):
- username, password = get_localhost_auth()
d = client.connect('localhost', self.listen_port, username='invalid-user')
def on_failure(failure):
- self.assertEqual(failure.trap(error.BadLoginError), error.BadLoginError)
- self.assertEqual(failure.value.message, 'Username does not exist')
- self.addCleanup(client.disconnect)
+ assert failure.trap(error.BadLoginError) == error.BadLoginError
+ assert failure.value.message == 'Username does not exist'
d.addCallbacks(self.fail, on_failure)
return d
@@ -146,16 +125,16 @@ class ClientTestCase(BaseTestCase, DaemonBase):
d = client.connect('localhost', self.listen_port, username=username)
def on_failure(failure):
- self.assertEqual(
- failure.trap(error.AuthenticationRequired), error.AuthenticationRequired
+ assert (
+ failure.trap(error.AuthenticationRequired)
+ == error.AuthenticationRequired
)
- self.assertEqual(failure.value.username, username)
- self.addCleanup(client.disconnect)
+ assert failure.value.username == username
d.addCallbacks(self.fail, on_failure)
return d
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_connect_with_password(self):
username, password = get_localhost_auth()
yield client.connect(
@@ -166,19 +145,15 @@ class ClientTestCase(BaseTestCase, DaemonBase):
ret = yield client.connect(
'localhost', self.listen_port, username='testuser', password='testpw'
)
- self.assertEqual(ret, AUTH_LEVEL_NORMAL)
- yield
+ assert ret == AUTH_LEVEL_NORMAL
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_invalid_rpc_method_call(self):
yield client.connect('localhost', self.listen_port, username='', password='')
d = client.core.invalid_method()
def on_failure(failure):
- self.assertEqual(
- failure.trap(error.WrappedException), error.WrappedException
- )
- self.addCleanup(client.disconnect)
+ assert failure.trap(error.WrappedException) == error.WrappedException
d.addCallbacks(self.fail, on_failure)
yield d
@@ -191,10 +166,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
)
def on_failure(failure):
- self.assertEqual(
- failure.trap(error.IncompatibleClient), error.IncompatibleClient
- )
- self.addCleanup(no_version_sending_client.disconnect)
+ assert failure.trap(error.IncompatibleClient) == error.IncompatibleClient
d.addCallbacks(self.fail, on_failure)
return d
diff --git a/deluge/tests/test_common.py b/deluge/tests/test_common.py
index 4f6aa2fd4..780d368ef 100644
--- a/deluge/tests/test_common.py
+++ b/deluge/tests/test_common.py
@@ -1,17 +1,15 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os
import sys
import tarfile
+from urllib.parse import quote_plus
-from twisted.trial import unittest
+import pytest
from deluge.common import (
VersionSplit,
@@ -22,8 +20,11 @@ from deluge.common import (
fsize,
fspeed,
ftime,
+ get_magnet_info,
get_path_size,
is_infohash,
+ is_interface,
+ is_interface_name,
is_ip,
is_ipv4,
is_ipv6,
@@ -31,113 +32,123 @@ from deluge.common import (
is_url,
windows_check,
)
-from deluge.i18n import setup_translation
-
-from .common import get_test_data_file, set_tmp_config_dir
-
-class CommonTestCase(unittest.TestCase):
- def setUp(self): # NOQA
- self.config_dir = set_tmp_config_dir()
- setup_translation()
+from .common import get_test_data_file
- def tearDown(self): # NOQA
- pass
+class TestCommon:
def test_fsize(self):
- self.assertEqual(fsize(0), '0 B')
- self.assertEqual(fsize(100), '100 B')
- self.assertEqual(fsize(1023), '1023 B')
- self.assertEqual(fsize(1024), '1.0 KiB')
- self.assertEqual(fsize(1048575), '1024.0 KiB')
- self.assertEqual(fsize(1048576), '1.0 MiB')
- self.assertEqual(fsize(1073741823), '1024.0 MiB')
- self.assertEqual(fsize(1073741824), '1.0 GiB')
- self.assertEqual(fsize(112245), '109.6 KiB')
- self.assertEqual(fsize(110723441824), '103.1 GiB')
- self.assertEqual(fsize(1099511627775), '1024.0 GiB')
- self.assertEqual(fsize(1099511627777), '1.0 TiB')
- self.assertEqual(fsize(766148267453245), '696.8 TiB')
+ assert fsize(0) == '0 B'
+ assert fsize(100) == '100 B'
+ assert fsize(1023) == '1023 B'
+ assert fsize(1024) == '1.0 KiB'
+ assert fsize(1048575) == '1024.0 KiB'
+ assert fsize(1048576) == '1.0 MiB'
+ assert fsize(1073741823) == '1024.0 MiB'
+ assert fsize(1073741824) == '1.0 GiB'
+ assert fsize(112245) == '109.6 KiB'
+ assert fsize(110723441824) == '103.1 GiB'
+ assert fsize(1099511627775) == '1024.0 GiB'
+ assert fsize(1099511627777) == '1.0 TiB'
+ assert fsize(766148267453245) == '696.8 TiB'
def test_fpcnt(self):
- self.assertTrue(fpcnt(0.9311) == '93.11%')
+ assert fpcnt(0.9311) == '93.11%'
def test_fspeed(self):
- self.assertTrue(fspeed(43134) == '42.1 KiB/s')
+ assert fspeed(43134) == '42.1 KiB/s'
def test_fpeer(self):
- self.assertTrue(fpeer(10, 20) == '10 (20)')
- self.assertTrue(fpeer(10, -1) == '10')
+ assert fpeer(10, 20) == '10 (20)'
+ assert fpeer(10, -1) == '10'
def test_ftime(self):
- self.assertEqual(ftime(0), '')
- self.assertEqual(ftime(5), '5s')
- self.assertEqual(ftime(100), '1m 40s')
- self.assertEqual(ftime(3789), '1h 3m')
- self.assertEqual(ftime(23011), '6h 23m')
- self.assertEqual(ftime(391187), '4d 12h')
- self.assertEqual(ftime(604800), '1w 0d')
- self.assertEqual(ftime(13893086), '22w 6d')
- self.assertEqual(ftime(59740269), '1y 46w')
- self.assertEqual(ftime(61.25), '1m 1s')
- self.assertEqual(ftime(119.9), '1m 59s')
+ assert ftime(0) == ''
+ assert ftime(5) == '5s'
+ assert ftime(100) == '1m 40s'
+ assert ftime(3789) == '1h 3m'
+ assert ftime(23011) == '6h 23m'
+ assert ftime(391187) == '4d 12h'
+ assert ftime(604800) == '1w 0d'
+ assert ftime(13893086) == '22w 6d'
+ assert ftime(59740269) == '1y 46w'
+ assert ftime(61.25) == '1m 1s'
+ assert ftime(119.9) == '1m 59s'
def test_fdate(self):
- self.assertTrue(fdate(-1) == '')
+ assert fdate(-1) == ''
def test_is_url(self):
- self.assertTrue(is_url('http://deluge-torrent.org'))
- self.assertFalse(is_url('file://test.torrent'))
+ assert is_url('http://deluge-torrent.org')
+ assert not is_url('file://test.torrent')
def test_is_magnet(self):
- self.assertTrue(
- is_magnet('magnet:?xt=urn:btih:SU5225URMTUEQLDXQWRB2EQWN6KLTYKN')
- )
- self.assertFalse(is_magnet(None))
+ assert is_magnet('magnet:?xt=urn:btih:SU5225URMTUEQLDXQWRB2EQWN6KLTYKN')
+ assert not is_magnet(None)
def test_is_infohash(self):
- self.assertTrue(is_infohash('2dc5d0e71a66fe69649a640d39cb00a259704973'))
+ assert is_infohash('2dc5d0e71a66fe69649a640d39cb00a259704973')
def test_get_path_size(self):
if windows_check() and sys.version_info < (3, 8):
# https://bugs.python.org/issue1311
- raise unittest.SkipTest('os.devnull returns False on Windows')
- self.assertTrue(get_path_size(os.devnull) == 0)
- self.assertTrue(get_path_size('non-existant.file') == -1)
+ pytest.skip('os.devnull returns False on Windows')
+ assert get_path_size(os.devnull) == 0
+ assert get_path_size('non-existant.file') == -1
def test_is_ip(self):
- self.assertTrue(is_ip('192.0.2.0'))
- self.assertFalse(is_ip('192..0.0'))
- self.assertTrue(is_ip('2001:db8::'))
- self.assertFalse(is_ip('2001:db8:'))
+ assert is_ip('192.0.2.0')
+ assert not is_ip('192..0.0')
+ assert is_ip('2001:db8::')
+ assert not is_ip('2001:db8:')
def test_is_ipv4(self):
- self.assertTrue(is_ipv4('192.0.2.0'))
- self.assertFalse(is_ipv4('192..0.0'))
+ assert is_ipv4('192.0.2.0')
+ assert not is_ipv4('192..0.0')
def test_is_ipv6(self):
- self.assertTrue(is_ipv6('2001:db8::'))
- self.assertFalse(is_ipv6('2001:db8:'))
+ assert is_ipv6('2001:db8::')
+ assert not is_ipv6('2001:db8:')
+
+ def test_is_interface_name(self):
+ if windows_check():
+ assert not is_interface_name('2001:db8:')
+ assert not is_interface_name('{THIS0000-IS00-ONLY-FOR0-TESTING00000}')
+ else:
+ assert is_interface_name('lo')
+ assert not is_interface_name('127.0.0.1')
+ assert not is_interface_name('eth01101')
+
+ def test_is_interface(self):
+ if windows_check():
+ assert is_interface('127.0.0.1')
+ assert not is_interface('127')
+ assert not is_interface('{THIS0000-IS00-ONLY-FOR0-TESTING00000}')
+ else:
+ assert is_interface('lo')
+ assert is_interface('127.0.0.1')
+ assert not is_interface('127.')
+ assert not is_interface('eth01101')
def test_version_split(self):
- self.assertTrue(VersionSplit('1.2.2') == VersionSplit('1.2.2'))
- self.assertTrue(VersionSplit('1.2.1') < VersionSplit('1.2.2'))
- self.assertTrue(VersionSplit('1.1.9') < VersionSplit('1.2.2'))
- self.assertTrue(VersionSplit('1.2.2') > VersionSplit('1.2.1'))
- self.assertTrue(VersionSplit('1.2.2') > VersionSplit('1.2.2-dev0'))
- self.assertTrue(VersionSplit('1.2.2-dev') < VersionSplit('1.3.0-rc2'))
- self.assertTrue(VersionSplit('1.2.2') > VersionSplit('1.2.2-rc2'))
- self.assertTrue(VersionSplit('1.2.2-rc2-dev') < VersionSplit('1.2.2-rc2'))
- self.assertTrue(VersionSplit('1.2.2-rc3') > VersionSplit('1.2.2-rc2'))
- self.assertTrue(VersionSplit('0.14.9') == VersionSplit('0.14.9'))
- self.assertTrue(VersionSplit('0.14.9') > VersionSplit('0.14.5'))
- self.assertTrue(VersionSplit('0.14.10') >= VersionSplit('0.14.9'))
- self.assertTrue(VersionSplit('1.4.0') > VersionSplit('1.3.900.dev123'))
- self.assertTrue(VersionSplit('1.3.2rc2.dev1') < VersionSplit('1.3.2-rc2'))
- self.assertTrue(VersionSplit('1.3.900.dev888') > VersionSplit('1.3.900.dev123'))
- self.assertTrue(VersionSplit('1.4.0') > VersionSplit('1.4.0.dev123'))
- self.assertTrue(VersionSplit('1.4.0.dev1') < VersionSplit('1.4.0'))
- self.assertTrue(VersionSplit('1.4.0a1') < VersionSplit('1.4.0'))
+ assert VersionSplit('1.2.2') == VersionSplit('1.2.2')
+ assert VersionSplit('1.2.1') < VersionSplit('1.2.2')
+ assert VersionSplit('1.1.9') < VersionSplit('1.2.2')
+ assert VersionSplit('1.2.2') > VersionSplit('1.2.1')
+ assert VersionSplit('1.2.2') > VersionSplit('1.2.2-dev0')
+ assert VersionSplit('1.2.2-dev') < VersionSplit('1.3.0-rc2')
+ assert VersionSplit('1.2.2') > VersionSplit('1.2.2-rc2')
+ assert VersionSplit('1.2.2-rc2-dev') < VersionSplit('1.2.2-rc2')
+ assert VersionSplit('1.2.2-rc3') > VersionSplit('1.2.2-rc2')
+ assert VersionSplit('0.14.9') == VersionSplit('0.14.9')
+ assert VersionSplit('0.14.9') > VersionSplit('0.14.5')
+ assert VersionSplit('0.14.10') >= VersionSplit('0.14.9')
+ assert VersionSplit('1.4.0') > VersionSplit('1.3.900.dev123')
+ assert VersionSplit('1.3.2rc2.dev1') < VersionSplit('1.3.2-rc2')
+ assert VersionSplit('1.3.900.dev888') > VersionSplit('1.3.900.dev123')
+ assert VersionSplit('1.4.0') > VersionSplit('1.4.0.dev123')
+ assert VersionSplit('1.4.0.dev1') < VersionSplit('1.4.0')
+ assert VersionSplit('1.4.0a1') < VersionSplit('1.4.0')
def test_parse_human_size(self):
from deluge.common import parse_human_size
@@ -150,17 +161,15 @@ class CommonTestCase(unittest.TestCase):
('1 MiB', 2 ** (10 * 2)),
('1 GiB', 2 ** (10 * 3)),
('1 GiB', 2 ** (10 * 3)),
- ('1M', 10 ** 6),
- ('1MB', 10 ** 6),
- ('1 GB', 10 ** 9),
- ('1 TB', 10 ** 12),
+ ('1M', 10**6),
+ ('1MB', 10**6),
+ ('1 GB', 10**9),
+ ('1 TB', 10**12),
]
for human_size, byte_size in sizes:
parsed = parse_human_size(human_size)
- self.assertEqual(
- parsed, byte_size, 'Mismatch when converting: %s' % human_size
- )
+ assert parsed == byte_size, 'Mismatch when converting: %s' % human_size
def test_archive_files(self):
arc_filelist = [
@@ -171,10 +180,10 @@ class CommonTestCase(unittest.TestCase):
with tarfile.open(arc_filepath, 'r') as tar:
for tar_info in tar:
- self.assertTrue(tar_info.isfile())
- self.assertTrue(
- tar_info.name in [os.path.basename(arcf) for arcf in arc_filelist]
- )
+ assert tar_info.isfile()
+ assert tar_info.name in [
+ os.path.basename(arcf) for arcf in arc_filelist
+ ]
def test_archive_files_missing(self):
"""Archive exists even with file not found."""
@@ -185,8 +194,8 @@ class CommonTestCase(unittest.TestCase):
filelist.remove('missing.file')
with tarfile.open(arc_filepath, 'r') as tar:
- self.assertEqual(tar.getnames(), filelist)
- self.assertTrue(all(tarinfo.isfile() for tarinfo in tar))
+ assert tar.getnames() == filelist
+ assert all(tarinfo.isfile() for tarinfo in tar)
def test_archive_files_message(self):
filelist = ['test.torrent', 'deluge.png']
@@ -196,9 +205,22 @@ class CommonTestCase(unittest.TestCase):
result_files = filelist + ['archive_message.txt']
with tarfile.open(arc_filepath, 'r') as tar:
- self.assertEqual(tar.getnames(), result_files)
+ assert tar.getnames() == result_files
for tar_info in tar:
- self.assertTrue(tar_info.isfile())
+ assert tar_info.isfile()
if tar_info.name == 'archive_message.txt':
result = tar.extractfile(tar_info).read().decode()
- self.assertEqual(result, 'test')
+ assert result == 'test'
+
+ def test_get_magnet_info_tiers(self):
+ tracker1 = 'udp://tracker1.example.com'
+ tracker2 = 'udp://tracker2.example.com'
+ magnet = (
+ 'magnet:?xt=urn:btih:SU5225URMTUEQLDXQWRB2EQWN6KLTYKN'
+ f'&tr.1={quote_plus(tracker1)}'
+ f'&tr.2={quote_plus(tracker2)}'
+ )
+ result = get_magnet_info(magnet)
+ assert result['info_hash'] == '953bad769164e8482c7785a21d12166f94b9e14d'
+ assert result['trackers'][tracker1] == 1
+ assert result['trackers'][tracker2] == 2
diff --git a/deluge/tests/test_component.py b/deluge/tests/test_component.py
index 26f24ad00..0345e24d3 100644
--- a/deluge/tests/test_component.py
+++ b/deluge/tests/test_component.py
@@ -1,19 +1,15 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
+import pytest
+import pytest_twisted
from twisted.internet import defer, threads
-from twisted.trial.unittest import SkipTest
import deluge.component as component
-from .basetest import BaseTestCase
-
class ComponentTester(component.Component):
def __init__(self, name, depend=None):
@@ -70,14 +66,15 @@ class ComponentTesterShutdown(component.Component):
self.stop_count += 1
-class ComponentTestClass(BaseTestCase):
+@pytest.mark.usefixtures('component')
+class TestComponent:
def tear_down(self):
return component.shutdown()
def test_start_component(self):
def on_start(result, c):
- self.assertEqual(c._component_state, 'Started')
- self.assertEqual(c.start_count, 1)
+ assert c._component_state == 'Started'
+ assert c.start_count == 1
c = ComponentTester('test_start_c1')
d = component.start(['test_start_c1'])
@@ -86,16 +83,16 @@ class ComponentTestClass(BaseTestCase):
def test_start_stop_depends(self):
def on_stop(result, c1, c2):
- self.assertEqual(c1._component_state, 'Stopped')
- self.assertEqual(c2._component_state, 'Stopped')
- self.assertEqual(c1.stop_count, 1)
- self.assertEqual(c2.stop_count, 1)
+ assert c1._component_state == 'Stopped'
+ assert c2._component_state == 'Stopped'
+ assert c1.stop_count == 1
+ assert c2.stop_count == 1
def on_start(result, c1, c2):
- self.assertEqual(c1._component_state, 'Started')
- self.assertEqual(c2._component_state, 'Started')
- self.assertEqual(c1.start_count, 1)
- self.assertEqual(c2.start_count, 1)
+ assert c1._component_state == 'Started'
+ assert c2._component_state == 'Started'
+ assert c1.start_count == 1
+ assert c2.start_count == 1
return component.stop(['test_start_depends_c1']).addCallback(
on_stop, c1, c2
)
@@ -126,8 +123,8 @@ class ComponentTestClass(BaseTestCase):
def test_start_all(self):
def on_start(*args):
for c in args[1:]:
- self.assertEqual(c._component_state, 'Started')
- self.assertEqual(c.start_count, 1)
+ assert c._component_state == 'Started'
+ assert c.start_count == 1
ret = self.start_with_depends()
ret[0].addCallback(on_start, *ret[1:])
@@ -136,20 +133,19 @@ class ComponentTestClass(BaseTestCase):
def test_register_exception(self):
ComponentTester('test_register_exception_c1')
- self.assertRaises(
- component.ComponentAlreadyRegistered,
- ComponentTester,
- 'test_register_exception_c1',
- )
+ with pytest.raises(component.ComponentAlreadyRegistered):
+ ComponentTester(
+ 'test_register_exception_c1',
+ )
def test_stop_component(self):
def on_stop(result, c):
- self.assertEqual(c._component_state, 'Stopped')
- self.assertFalse(c._component_timer.running)
- self.assertEqual(c.stop_count, 1)
+ assert c._component_state == 'Stopped'
+ assert not c._component_timer.running
+ assert c.stop_count == 1
def on_start(result, c):
- self.assertEqual(c._component_state, 'Started')
+ assert c._component_state == 'Started'
return component.stop(['test_stop_component_c1']).addCallback(on_stop, c)
c = ComponentTesterUpdate('test_stop_component_c1')
@@ -160,12 +156,12 @@ class ComponentTestClass(BaseTestCase):
def test_stop_all(self):
def on_stop(result, *args):
for c in args:
- self.assertEqual(c._component_state, 'Stopped')
- self.assertEqual(c.stop_count, 1)
+ assert c._component_state == 'Stopped'
+ assert c.stop_count == 1
def on_start(result, *args):
for c in args:
- self.assertEqual(c._component_state, 'Started')
+ assert c._component_state == 'Started'
return component.stop().addCallback(on_stop, *args)
ret = self.start_with_depends()
@@ -175,9 +171,9 @@ class ComponentTestClass(BaseTestCase):
def test_update(self):
def on_start(result, c1, counter):
- self.assertTrue(c1._component_timer)
- self.assertTrue(c1._component_timer.running)
- self.assertNotEqual(c1.counter, counter)
+ assert c1._component_timer
+ assert c1._component_timer.running
+ assert c1.counter != counter
return component.stop()
c1 = ComponentTesterUpdate('test_update_c1')
@@ -189,13 +185,13 @@ class ComponentTestClass(BaseTestCase):
def test_pause(self):
def on_pause(result, c1, counter):
- self.assertEqual(c1._component_state, 'Paused')
- self.assertNotEqual(c1.counter, counter)
- self.assertFalse(c1._component_timer.running)
+ assert c1._component_state == 'Paused'
+ assert c1.counter != counter
+ assert not c1._component_timer.running
def on_start(result, c1, counter):
- self.assertTrue(c1._component_timer)
- self.assertNotEqual(c1.counter, counter)
+ assert c1._component_timer
+ assert c1.counter != counter
d = component.pause(['test_pause_c1'])
d.addCallback(on_pause, c1, counter)
return d
@@ -207,23 +203,16 @@ class ComponentTestClass(BaseTestCase):
d.addCallback(on_start, c1, cnt)
return d
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_component_start_error(self):
ComponentTesterUpdate('test_pause_c1')
yield component.start(['test_pause_c1'])
yield component.pause(['test_pause_c1'])
test_comp = component.get('test_pause_c1')
- try:
- result = self.failureResultOf(test_comp._component_start())
- except AttributeError:
- raise SkipTest(
- 'This test requires trial failureResultOf() in Twisted version >= 13'
- )
- self.assertEqual(
- result.check(component.ComponentException), component.ComponentException
- )
+ with pytest.raises(component.ComponentException, match='Current state: Paused'):
+ yield test_comp._component_start()
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_start_paused_error(self):
ComponentTesterUpdate('test_pause_c1')
yield component.start(['test_pause_c1'])
@@ -232,29 +221,26 @@ class ComponentTestClass(BaseTestCase):
# Deferreds that fail in component have to error handler which results in
# twisted doing a log.err call which causes the test to fail.
# Prevent failure by ignoring the exception
- self._observer._ignoreErrors(component.ComponentException)
+ # self._observer._ignoreErrors(component.ComponentException)
result = yield component.start()
- self.assertEqual(
- [(result[0][0], result[0][1].value)],
- [
- (
- defer.FAILURE,
- component.ComponentException(
- 'Trying to start component "%s" but it is '
- 'not in a stopped state. Current state: %s'
- % ('test_pause_c1', 'Paused'),
- '',
- ),
- )
- ],
- )
+ assert [(result[0][0], result[0][1].value)] == [
+ (
+ defer.FAILURE,
+ component.ComponentException(
+ 'Trying to start component "%s" but it is '
+ 'not in a stopped state. Current state: %s'
+ % ('test_pause_c1', 'Paused'),
+ '',
+ ),
+ )
+ ]
def test_shutdown(self):
def on_shutdown(result, c1):
- self.assertTrue(c1.shutdowned)
- self.assertEqual(c1._component_state, 'Stopped')
- self.assertEqual(c1.stop_count, 1)
+ assert c1.shutdowned
+ assert c1._component_state == 'Stopped'
+ assert c1.stop_count == 1
def on_start(result, c1):
d = component.shutdown()
diff --git a/deluge/tests/test_config.py b/deluge/tests/test_config.py
index 0f6df3bb5..2840dbf5b 100644
--- a/deluge/tests/test_config.py
+++ b/deluge/tests/test_config.py
@@ -1,23 +1,21 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
+import json
+import logging
import os
from codecs import getwriter
+import pytest
+import pytest_twisted
from twisted.internet import task
-from twisted.trial import unittest
-import deluge.config
from deluge.common import JSON_FORMAT
from deluge.config import Config
-
-from .common import set_tmp_config_dir
+from deluge.ui.hostlist import mask_hosts_password
DEFAULTS = {
'string': 'foobar',
@@ -26,37 +24,42 @@ DEFAULTS = {
'bool': True,
'unicode': 'foobar',
'password': 'abc123*\\[!]?/<>#{@}=|"+$%(^)~',
+ 'hosts': [
+ ('host1', 'port', '', 'password1234'),
+ ('host2', 'port', '', 'password5678'),
+ ],
}
-class ConfigTestCase(unittest.TestCase):
- def setUp(self): # NOQA: N803
- self.config_dir = set_tmp_config_dir()
+LOGGER = logging.getLogger(__name__)
+
+class TestConfig:
def test_init(self):
config = Config('test.conf', defaults=DEFAULTS, config_dir=self.config_dir)
- self.assertEqual(DEFAULTS, config.config)
+ assert DEFAULTS == config.config
config = Config('test.conf', config_dir=self.config_dir)
- self.assertEqual({}, config.config)
+ assert {} == config.config
def test_set_get_item(self):
config = Config('test.conf', config_dir=self.config_dir)
config['foo'] = 1
- self.assertEqual(config['foo'], 1)
- self.assertRaises(ValueError, config.set_item, 'foo', 'bar')
+ assert config['foo'] == 1
+ with pytest.raises(ValueError):
+ config.set_item('foo', 'bar')
config['foo'] = 2
- self.assertEqual(config.get_item('foo'), 2)
+ assert config.get_item('foo') == 2
config['foo'] = '3'
- self.assertEqual(config.get_item('foo'), 3)
+ assert config.get_item('foo') == 3
config['unicode'] = 'ВИДЕОФИЛЬМЫ'
- self.assertEqual(config['unicode'], 'ВИДЕОФИЛЬМЫ')
+ assert config['unicode'] == 'ВИДЕОФИЛЬМЫ'
config['unicode'] = b'foostring'
- self.assertFalse(isinstance(config.get_item('unicode'), bytes))
+ assert not isinstance(config.get_item('unicode'), bytes)
config._save_timer.cancel()
@@ -64,43 +67,103 @@ class ConfigTestCase(unittest.TestCase):
config = Config('test.conf', config_dir=self.config_dir)
config['foo'] = None
- self.assertIsNone(config['foo'])
- self.assertIsInstance(config['foo'], type(None))
+ assert config['foo'] is None
+ assert isinstance(config['foo'], type(None))
config['foo'] = 1
- self.assertEqual(config.get('foo'), 1)
+ assert config.get('foo') == 1
config['foo'] = None
- self.assertIsNone(config['foo'])
+ assert config['foo'] is None
config['bar'] = None
- self.assertIsNone(config['bar'])
+ assert config['bar'] is None
config['bar'] = None
- self.assertIsNone(config['bar'])
+ assert config['bar'] is None
config._save_timer.cancel()
+ @pytest_twisted.ensureDeferred
+ async def test_on_changed_callback(self, mock_callback):
+ config = Config('test.conf', config_dir=self.config_dir)
+ config.register_change_callback(mock_callback)
+ config['foo'] = 1
+ assert config['foo'] == 1
+ await mock_callback.deferred
+ mock_callback.assert_called_once_with('foo', 1)
+
+ @pytest_twisted.ensureDeferred
+ async def test_key_function_callback(self, mock_callback):
+ config = Config(
+ 'test.conf', defaults={'foo': 1, 'bar': 1}, config_dir=self.config_dir
+ )
+
+ assert config['foo'] == 1
+ config.register_set_function('foo', mock_callback)
+ await mock_callback.deferred
+ mock_callback.assert_called_once_with('foo', 1)
+
+ mock_callback.reset_mock()
+ config.register_set_function('bar', mock_callback, apply_now=False)
+ mock_callback.assert_not_called()
+ config['bar'] = 2
+ await mock_callback.deferred
+ mock_callback.assert_called_once_with('bar', 2)
+
def test_get(self):
config = Config('test.conf', config_dir=self.config_dir)
config['foo'] = 1
- self.assertEqual(config.get('foo'), 1)
- self.assertEqual(config.get('foobar'), None)
- self.assertEqual(config.get('foobar', 2), 2)
+ assert config.get('foo') == 1
+ assert config.get('foobar') is None
+ assert config.get('foobar', 2) == 2
config['foobar'] = 5
- self.assertEqual(config.get('foobar', 2), 5)
+ assert config.get('foobar', 2) == 5
+
+ def test_set_log_mask_funcs(self, caplog):
+ """Test mask func masks key in log"""
+ caplog.set_level(logging.DEBUG)
+ config = Config(
+ 'test.conf',
+ config_dir=self.config_dir,
+ log_mask_funcs={'hosts': mask_hosts_password},
+ )
+ config['hosts'] = DEFAULTS['hosts']
+ assert isinstance(config['hosts'], list)
+ assert 'host1' in caplog.text
+ assert 'host2' in caplog.text
+ assert 'password1234' not in caplog.text
+ assert 'password5678' not in caplog.text
+ assert '*' * 10 in caplog.text
+
+ def test_load_log_mask_funcs(self, caplog):
+ """Test mask func masks key in log"""
+ with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file:
+ json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT)
+
+ config = Config(
+ 'test.conf',
+ config_dir=self.config_dir,
+ log_mask_funcs={'hosts': mask_hosts_password},
+ )
+ with caplog.at_level(logging.DEBUG):
+ config.load(os.path.join(self.config_dir, 'test.conf'))
+ assert 'host1' in caplog.text
+ assert 'host2' in caplog.text
+ assert 'foobar' in caplog.text
+ assert 'password1234' not in caplog.text
+ assert 'password5678' not in caplog.text
+ assert '*' * 10 in caplog.text
def test_load(self):
def check_config():
config = Config('test.conf', config_dir=self.config_dir)
- self.assertEqual(config['string'], 'foobar')
- self.assertEqual(config['float'], 0.435)
- self.assertEqual(config['password'], 'abc123*\\[!]?/<>#{@}=|"+$%(^)~')
+ assert config['string'] == 'foobar'
+ assert config['float'] == 0.435
+ assert config['password'] == 'abc123*\\[!]?/<>#{@}=|"+$%(^)~'
# Test opening a previous 1.2 config file of just a json object
- import json
-
with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file:
json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT)
@@ -128,38 +191,38 @@ class ConfigTestCase(unittest.TestCase):
# We do this twice because the first time we need to save the file to disk
# and the second time we do a compare and we should not write
ret = config.save()
- self.assertTrue(ret)
+ assert ret
ret = config.save()
- self.assertTrue(ret)
+ assert ret
config['string'] = 'baz'
config['int'] = 2
ret = config.save()
- self.assertTrue(ret)
+ assert ret
del config
config = Config('test.conf', defaults=DEFAULTS, config_dir=self.config_dir)
- self.assertEqual(config['string'], 'baz')
- self.assertEqual(config['int'], 2)
+ assert config['string'] == 'baz'
+ assert config['int'] == 2
def test_save_timer(self):
- self.clock = task.Clock()
- deluge.config.callLater = self.clock.callLater
+ clock = task.Clock()
config = Config('test.conf', defaults=DEFAULTS, config_dir=self.config_dir)
+ config.callLater = clock.callLater
config['string'] = 'baz'
config['int'] = 2
- self.assertTrue(config._save_timer.active())
+ assert config._save_timer.active()
# Timeout set for 5 seconds in config, so lets move clock by 5 seconds
- self.clock.advance(5)
+ clock.advance(5)
def check_config(config):
- self.assertTrue(not config._save_timer.active())
+ assert not config._save_timer.active()
del config
config = Config('test.conf', defaults=DEFAULTS, config_dir=self.config_dir)
- self.assertEqual(config['string'], 'baz')
- self.assertEqual(config['int'], 2)
+ assert config['string'] == 'baz'
+ assert config['int'] == 2
check_config(config)
@@ -176,7 +239,7 @@ class ConfigTestCase(unittest.TestCase):
from deluge.config import find_json_objects
objects = find_json_objects(s)
- self.assertEqual(len(objects), 2)
+ assert len(objects) == 2
def test_find_json_objects_curly_brace(self):
"""Test with string containing curly brace"""
@@ -193,7 +256,7 @@ class ConfigTestCase(unittest.TestCase):
from deluge.config import find_json_objects
objects = find_json_objects(s)
- self.assertEqual(len(objects), 2)
+ assert len(objects) == 2
def test_find_json_objects_double_quote(self):
"""Test with string containing double quote"""
@@ -211,4 +274,4 @@ class ConfigTestCase(unittest.TestCase):
from deluge.config import find_json_objects
objects = find_json_objects(s)
- self.assertEqual(len(objects), 2)
+ assert len(objects) == 2
diff --git a/deluge/tests/test_core.py b/deluge/tests/test_core.py
index 15fbc1bcf..6a3fb9506 100644
--- a/deluge/tests/test_core.py
+++ b/deluge/tests/test_core.py
@@ -1,20 +1,17 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
+import os
from base64 import b64encode
from hashlib import sha1 as sha
import pytest
-from six import integer_types
+import pytest_twisted
from twisted.internet import defer, reactor, task
from twisted.internet.error import CannotListenError
-from twisted.python.failure import Failure
from twisted.web.http import FORBIDDEN
from twisted.web.resource import EncodingResourceWrapper, Resource
from twisted.web.server import GzipEncoderFactory, Site
@@ -24,12 +21,12 @@ import deluge.common
import deluge.component as component
import deluge.core.torrent
from deluge._libtorrent import lt
+from deluge.conftest import BaseTestCase
from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer
from deluge.error import AddTorrentError, InvalidTorrentError
from . import common
-from .basetest import BaseTestCase
common.disable_new_release_check()
@@ -80,14 +77,13 @@ class TopLevelResource(Resource):
)
-class CoreTestCase(BaseTestCase):
+class TestCore(BaseTestCase):
def set_up(self):
- common.set_tmp_config_dir()
self.rpcserver = RPCServer(listen=False)
- self.core = Core()
+ self.core: Core = Core()
self.core.config.config['lsd'] = False
self.clock = task.Clock()
- self.core.torrentmanager.callLater = self.clock.callLater
+ self.core.torrentmanager.clock = self.clock
self.listen_port = 51242
return component.start().addCallback(self.start_web_server)
@@ -131,7 +127,7 @@ class CoreTestCase(BaseTestCase):
torrent_id = self.core.add_torrent_file(filename, filedump, options)
return torrent_id
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_add_torrent_files(self):
options = {}
filenames = ['test.torrent', 'test_torrent.file.torrent']
@@ -142,9 +138,9 @@ class CoreTestCase(BaseTestCase):
filedump = b64encode(_file.read())
files_to_add.append((filename, filedump, options))
errors = yield self.core.add_torrent_files(files_to_add)
- self.assertEqual(len(errors), 0)
+ assert len(errors) == 0
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_add_torrent_files_error_duplicate(self):
options = {}
filenames = ['test.torrent', 'test.torrent']
@@ -155,10 +151,10 @@ class CoreTestCase(BaseTestCase):
filedump = b64encode(_file.read())
files_to_add.append((filename, filedump, options))
errors = yield self.core.add_torrent_files(files_to_add)
- self.assertEqual(len(errors), 1)
- self.assertTrue(str(errors[0]).startswith('Torrent already in session'))
+ assert len(errors) == 1
+ assert str(errors[0]).startswith('Torrent already in session')
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_add_torrent_file(self):
options = {}
filename = common.get_test_data_file('test.torrent')
@@ -171,17 +167,16 @@ class CoreTestCase(BaseTestCase):
with open(filename, 'rb') as _file:
info_hash = sha(bencode(bdecode(_file.read())[b'info'])).hexdigest()
- self.assertEqual(torrent_id, info_hash)
+ assert torrent_id == info_hash
def test_add_torrent_file_invalid_filedump(self):
options = {}
filename = common.get_test_data_file('test.torrent')
- self.assertRaises(
- AddTorrentError, self.core.add_torrent_file, filename, False, options
- )
+ with pytest.raises(AddTorrentError):
+ self.core.add_torrent_file(filename, False, options)
- @defer.inlineCallbacks
- def test_add_torrent_url(self):
+ @pytest_twisted.inlineCallbacks
+ def test_add_torrent_url(self, mock_mkstemp):
url = (
'http://localhost:%d/ubuntu-9.04-desktop-i386.iso.torrent'
% self.listen_port
@@ -190,78 +185,83 @@ class CoreTestCase(BaseTestCase):
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
torrent_id = yield self.core.add_torrent_url(url, options)
- self.assertEqual(torrent_id, info_hash)
+ assert torrent_id == info_hash
+ assert not os.path.isfile(mock_mkstemp[1])
- def test_add_torrent_url_with_cookie(self):
+ @pytest_twisted.ensureDeferred
+ async def test_add_torrent_url_with_cookie(self):
url = 'http://localhost:%d/cookie' % self.listen_port
options = {}
headers = {'Cookie': 'password=deluge'}
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
- d = self.core.add_torrent_url(url, options)
- d.addCallbacks(self.fail, self.assertIsInstance, errbackArgs=(Failure,))
+ with pytest.raises(Exception):
+ await self.core.add_torrent_url(url, options)
- d = self.core.add_torrent_url(url, options, headers)
- d.addCallbacks(self.assertEqual, self.fail, callbackArgs=(info_hash,))
-
- return d
+ result = await self.core.add_torrent_url(url, options, headers)
+ assert result == info_hash
- def test_add_torrent_url_with_redirect(self):
+ @pytest_twisted.ensureDeferred
+ async def test_add_torrent_url_with_redirect(self):
url = 'http://localhost:%d/redirect' % self.listen_port
options = {}
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
- d = self.core.add_torrent_url(url, options)
- d.addCallback(self.assertEqual, info_hash)
- return d
+ result = await self.core.add_torrent_url(url, options)
+ assert result == info_hash
- def test_add_torrent_url_with_partial_download(self):
+ @pytest_twisted.ensureDeferred
+ async def test_add_torrent_url_with_partial_download(self):
url = 'http://localhost:%d/partial' % self.listen_port
options = {}
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
- d = self.core.add_torrent_url(url, options)
- d.addCallback(self.assertEqual, info_hash)
- return d
+ result = await self.core.add_torrent_url(url, options)
+ assert result == info_hash
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_add_torrent_magnet(self):
info_hash = '60d5d82328b4547511fdeac9bf4d0112daa0ce00'
- uri = deluge.common.create_magnet_uri(info_hash)
+ tracker = 'udp://tracker.example.com'
+ name = 'test magnet'
+ uri = deluge.common.create_magnet_uri(info_hash, name=name, trackers=[tracker])
options = {}
torrent_id = yield self.core.add_torrent_magnet(uri, options)
- self.assertEqual(torrent_id, info_hash)
+ assert torrent_id == info_hash
+ torrent_status = self.core.get_torrent_status(torrent_id, ['name', 'trackers'])
+ assert torrent_status['trackers'][0]['url'] == tracker
+ assert torrent_status['name'] == name
def test_resume_torrent(self):
tid1 = self.add_torrent('test.torrent', paused=True)
tid2 = self.add_torrent('test_torrent.file.torrent', paused=True)
# Assert paused
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertTrue(r1['paused'])
+ assert r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertTrue(r2['paused'])
+ assert r2['paused']
self.core.resume_torrent(tid2)
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertTrue(r1['paused'])
+ assert r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertFalse(r2['paused'])
+ assert not r2['paused']
def test_resume_torrent_list(self):
"""Backward compatibility for list of torrent_ids."""
torrent_id = self.add_torrent('test.torrent', paused=True)
self.core.resume_torrent([torrent_id])
result = self.core.get_torrent_status(torrent_id, ['paused'])
- self.assertFalse(result['paused'])
+ assert not result['paused']
def test_resume_torrents(self):
tid1 = self.add_torrent('test.torrent', paused=True)
tid2 = self.add_torrent('test_torrent.file.torrent', paused=True)
self.core.resume_torrents([tid1, tid2])
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertFalse(r1['paused'])
+ assert not r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertFalse(r2['paused'])
+ assert not r2['paused']
def test_resume_torrents_all(self):
"""With no torrent_ids param, resume all torrents"""
@@ -269,33 +269,33 @@ class CoreTestCase(BaseTestCase):
tid2 = self.add_torrent('test_torrent.file.torrent', paused=True)
self.core.resume_torrents()
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertFalse(r1['paused'])
+ assert not r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertFalse(r2['paused'])
+ assert not r2['paused']
def test_pause_torrent(self):
tid1 = self.add_torrent('test.torrent')
tid2 = self.add_torrent('test_torrent.file.torrent')
# Assert not paused
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertFalse(r1['paused'])
+ assert not r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertFalse(r2['paused'])
+ assert not r2['paused']
self.core.pause_torrent(tid2)
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertFalse(r1['paused'])
+ assert not r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertTrue(r2['paused'])
+ assert r2['paused']
def test_pause_torrent_list(self):
"""Backward compatibility for list of torrent_ids."""
torrent_id = self.add_torrent('test.torrent')
result = self.core.get_torrent_status(torrent_id, ['paused'])
- self.assertFalse(result['paused'])
+ assert not result['paused']
self.core.pause_torrent([torrent_id])
result = self.core.get_torrent_status(torrent_id, ['paused'])
- self.assertTrue(result['paused'])
+ assert result['paused']
def test_pause_torrents(self):
tid1 = self.add_torrent('test.torrent')
@@ -303,9 +303,9 @@ class CoreTestCase(BaseTestCase):
self.core.pause_torrents([tid1, tid2])
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertTrue(r1['paused'])
+ assert r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertTrue(r2['paused'])
+ assert r2['paused']
def test_pause_torrents_all(self):
"""With no torrent_ids param, pause all torrents"""
@@ -314,26 +314,24 @@ class CoreTestCase(BaseTestCase):
self.core.pause_torrents()
r1 = self.core.get_torrent_status(tid1, ['paused'])
- self.assertTrue(r1['paused'])
+ assert r1['paused']
r2 = self.core.get_torrent_status(tid2, ['paused'])
- self.assertTrue(r2['paused'])
+ assert r2['paused']
+ @pytest_twisted.inlineCallbacks
def test_prefetch_metadata_existing(self):
"""Check another call with same magnet returns existing deferred."""
magnet = 'magnet:?xt=urn:btih:ab570cdd5a17ea1b61e970bb72047de141bce173'
- expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', None)
-
- def on_result(result):
- self.assertEqual(result, expected)
+ expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', b'')
- d = self.core.prefetch_magnet_metadata(magnet)
- d.addCallback(on_result)
+ d1 = self.core.prefetch_magnet_metadata(magnet)
d2 = self.core.prefetch_magnet_metadata(magnet)
- d2.addCallback(on_result)
+ dg = defer.gatherResults([d1, d2], consumeErrors=True)
self.clock.advance(30)
- return defer.DeferredList([d, d2])
+ result = yield dg
+ assert result == [expected] * 2
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_remove_torrent(self):
options = {}
filename = common.get_test_data_file('test.torrent')
@@ -341,18 +339,17 @@ class CoreTestCase(BaseTestCase):
filedump = b64encode(_file.read())
torrent_id = yield self.core.add_torrent_file_async(filename, filedump, options)
removed = self.core.remove_torrent(torrent_id, True)
- self.assertTrue(removed)
- self.assertEqual(len(self.core.get_session_state()), 0)
+ assert removed
+ assert len(self.core.get_session_state()) == 0
def test_remove_torrent_invalid(self):
- self.assertRaises(
- InvalidTorrentError,
- self.core.remove_torrent,
- 'torrentidthatdoesntexist',
- True,
- )
+ with pytest.raises(InvalidTorrentError):
+ self.core.remove_torrent(
+ 'torrentidthatdoesntexist',
+ True,
+ )
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_remove_torrents(self):
options = {}
filename = common.get_test_data_file('test.torrent')
@@ -369,17 +366,17 @@ class CoreTestCase(BaseTestCase):
d = self.core.remove_torrents([torrent_id, torrent_id2], True)
def test_ret(val):
- self.assertTrue(val == [])
+ assert val == []
d.addCallback(test_ret)
def test_session_state(val):
- self.assertEqual(len(self.core.get_session_state()), 0)
+ assert len(self.core.get_session_state()) == 0
d.addCallback(test_session_state)
yield d
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_remove_torrents_invalid(self):
options = {}
filename = common.get_test_data_file('test.torrent')
@@ -391,58 +388,53 @@ class CoreTestCase(BaseTestCase):
val = yield self.core.remove_torrents(
['invalidid1', 'invalidid2', torrent_id], False
)
- self.assertEqual(len(val), 2)
- self.assertEqual(
- val[0], ('invalidid1', 'torrent_id invalidid1 not in session.')
- )
- self.assertEqual(
- val[1], ('invalidid2', 'torrent_id invalidid2 not in session.')
- )
+ assert len(val) == 2
+ assert val[0] == ('invalidid1', 'torrent_id invalidid1 not in session.')
+ assert val[1] == ('invalidid2', 'torrent_id invalidid2 not in session.')
def test_get_session_status(self):
status = self.core.get_session_status(
['net.recv_tracker_bytes', 'net.sent_tracker_bytes']
)
- self.assertIsInstance(status, dict)
- self.assertEqual(status['net.recv_tracker_bytes'], 0)
- self.assertEqual(status['net.sent_tracker_bytes'], 0)
+ assert isinstance(status, dict)
+ assert status['net.recv_tracker_bytes'] == 0
+ assert status['net.sent_tracker_bytes'] == 0
def test_get_session_status_all(self):
status = self.core.get_session_status([])
- self.assertIsInstance(status, dict)
- self.assertIn('upload_rate', status)
- self.assertIn('net.recv_bytes', status)
+ assert isinstance(status, dict)
+ assert 'upload_rate' in status
+ assert 'net.recv_bytes' in status
def test_get_session_status_depr(self):
status = self.core.get_session_status(['num_peers', 'num_unchoked'])
- self.assertIsInstance(status, dict)
- self.assertEqual(status['num_peers'], 0)
- self.assertEqual(status['num_unchoked'], 0)
+ assert isinstance(status, dict)
+ assert status['num_peers'] == 0
+ assert status['num_unchoked'] == 0
def test_get_session_status_rates(self):
status = self.core.get_session_status(['upload_rate', 'download_rate'])
- self.assertIsInstance(status, dict)
- self.assertEqual(status['upload_rate'], 0)
+ assert isinstance(status, dict)
+ assert status['upload_rate'] == 0
def test_get_session_status_ratio(self):
status = self.core.get_session_status(['write_hit_ratio', 'read_hit_ratio'])
- self.assertIsInstance(status, dict)
- self.assertEqual(status['write_hit_ratio'], 0.0)
- self.assertEqual(status['read_hit_ratio'], 0.0)
+ assert isinstance(status, dict)
+ assert status['write_hit_ratio'] == 0.0
+ assert status['read_hit_ratio'] == 0.0
def test_get_free_space(self):
space = self.core.get_free_space('.')
- # get_free_space returns long on Python 2 (32-bit).
- self.assertTrue(isinstance(space, integer_types))
- self.assertTrue(space >= 0)
- self.assertEqual(self.core.get_free_space('/someinvalidpath'), -1)
+ assert isinstance(space, int)
+ assert space >= 0
+ assert self.core.get_free_space('/someinvalidpath') == -1
@pytest.mark.slow
def test_test_listen_port(self):
d = self.core.test_listen_port()
def result(r):
- self.assertTrue(r in (True, False))
+ assert r in (True, False)
d.addCallback(result)
return d
@@ -460,24 +452,22 @@ class CoreTestCase(BaseTestCase):
}
for key in pathlist:
- self.assertEqual(
- deluge.core.torrent.sanitize_filepath(key, folder=False), pathlist[key]
+ assert (
+ deluge.core.torrent.sanitize_filepath(key, folder=False)
+ == pathlist[key]
)
- self.assertEqual(
- deluge.core.torrent.sanitize_filepath(key, folder=True),
- pathlist[key] + '/',
+
+ assert (
+ deluge.core.torrent.sanitize_filepath(key, folder=True)
+ == pathlist[key] + '/'
)
def test_get_set_config_values(self):
- self.assertEqual(
- self.core.get_config_values(['abc', 'foo']), {'foo': None, 'abc': None}
- )
- self.assertEqual(self.core.get_config_value('foobar'), None)
+ assert self.core.get_config_values(['abc', 'foo']) == {'foo': None, 'abc': None}
+ assert self.core.get_config_value('foobar') is None
self.core.set_config({'abc': 'def', 'foo': 10, 'foobar': 'barfoo'})
- self.assertEqual(
- self.core.get_config_values(['foo', 'abc']), {'foo': 10, 'abc': 'def'}
- )
- self.assertEqual(self.core.get_config_value('foobar'), 'barfoo')
+ assert self.core.get_config_values(['foo', 'abc']) == {'foo': 10, 'abc': 'def'}
+ assert self.core.get_config_value('foobar') == 'barfoo'
def test_read_only_config_keys(self):
key = 'max_upload_speed'
@@ -486,13 +476,13 @@ class CoreTestCase(BaseTestCase):
old_value = self.core.get_config_value(key)
self.core.set_config({key: old_value + 10})
new_value = self.core.get_config_value(key)
- self.assertEqual(old_value, new_value)
+ assert old_value == new_value
self.core.read_only_config_keys = None
def test__create_peer_id(self):
- self.assertEqual(self.core._create_peer_id('2.0.0'), '-DE200s-')
- self.assertEqual(self.core._create_peer_id('2.0.0.dev15'), '-DE200D-')
- self.assertEqual(self.core._create_peer_id('2.0.1rc1'), '-DE201r-')
- self.assertEqual(self.core._create_peer_id('2.11.0b2'), '-DE2B0b-')
- self.assertEqual(self.core._create_peer_id('2.4.12b2.dev3'), '-DE24CD-')
+ assert self.core._create_peer_id('2.0.0') == '-DE200s-'
+ assert self.core._create_peer_id('2.0.0.dev15') == '-DE200D-'
+ assert self.core._create_peer_id('2.0.1rc1') == '-DE201r-'
+ assert self.core._create_peer_id('2.11.0b2') == '-DE2B0b-'
+ assert self.core._create_peer_id('2.4.12b2.dev3') == '-DE24CD-'
diff --git a/deluge/tests/test_decorators.py b/deluge/tests/test_decorators.py
index 7d4bd98c8..d2ecd1a2b 100644
--- a/deluge/tests/test_decorators.py
+++ b/deluge/tests/test_decorators.py
@@ -1,18 +1,14 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
-from twisted.trial import unittest
from deluge.decorators import proxy
-class DecoratorsTestCase(unittest.TestCase):
+class TestDecorators:
def test_proxy_with_simple_functions(self):
def negate(func, *args, **kwargs):
return not func(*args, **kwargs)
@@ -26,16 +22,16 @@ class DecoratorsTestCase(unittest.TestCase):
def double_nothing(_bool):
return _bool
- self.assertTrue(something(False))
- self.assertFalse(something(True))
- self.assertTrue(double_nothing(True))
- self.assertFalse(double_nothing(False))
+ assert something(False)
+ assert not something(True)
+ assert double_nothing(True)
+ assert not double_nothing(False)
def test_proxy_with_class_method(self):
def negate(func, *args, **kwargs):
return -func(*args, **kwargs)
- class Test(object):
+ class Test:
def __init__(self, number):
self.number = number
@@ -48,5 +44,5 @@ class DecoratorsTestCase(unittest.TestCase):
return self.diff(number)
t = Test(5)
- self.assertEqual(t.diff(1), -4)
- self.assertEqual(t.no_diff(1), 4)
+ assert t.diff(1) == -4
+ assert t.no_diff(1) == 4
diff --git a/deluge/tests/test_error.py b/deluge/tests/test_error.py
index c552e9422..a87d6a2d8 100644
--- a/deluge/tests/test_error.py
+++ b/deluge/tests/test_error.py
@@ -1,54 +1,39 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
-from twisted.trial import unittest
-
import deluge.error
-class ErrorTestCase(unittest.TestCase):
- def setUp(self): # NOQA: N803
- pass
-
- def tearDown(self): # NOQA: N803
- pass
-
+class TestError:
def test_deluge_error(self):
msg = 'Some message'
e = deluge.error.DelugeError(msg)
- self.assertEqual(str(e), msg)
+ assert str(e) == msg
from twisted.internet.defer import DebugInfo
del DebugInfo.__del__ # Hides all errors
- self.assertEqual(e._args, (msg,))
- self.assertEqual(e._kwargs, {})
+ assert e._args == (msg,)
+ assert e._kwargs == {}
def test_incompatible_client(self):
version = '1.3.6'
e = deluge.error.IncompatibleClient(version)
- self.assertEqual(
- str(e),
- 'Your deluge client is not compatible with the daemon. \
-Please upgrade your client to %s'
- % version,
+ assert (
+ str(e) == 'Your deluge client is not compatible with the daemon. '
+ 'Please upgrade your client to %s' % version
)
def test_not_authorized_error(self):
current_level = 5
required_level = 10
e = deluge.error.NotAuthorizedError(current_level, required_level)
- self.assertEqual(
- str(e), 'Auth level too low: %d < %d' % (current_level, required_level)
- )
+ assert str(e) == 'Auth level too low: %d < %d' % (current_level, required_level)
def test_bad_login_error(self):
message = 'Login failed'
username = 'deluge'
e = deluge.error.BadLoginError(message, username)
- self.assertEqual(str(e), message)
+ assert str(e) == message
diff --git a/deluge/tests/test_files_tab.py b/deluge/tests/test_files_tab.py
index 1ec8e18de..1e97cbbc3 100644
--- a/deluge/tests/test_files_tab.py
+++ b/deluge/tests/test_files_tab.py
@@ -1,23 +1,16 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import pytest
-from twisted.trial import unittest
import deluge.component as component
-from deluge.common import windows_check
from deluge.configmanager import ConfigManager
+from deluge.conftest import BaseTestCase
from deluge.i18n import setup_translation
-from . import common
-from .basetest import BaseTestCase
-
libs_available = True
# Allow running other tests without GTKUI dependencies available
try:
@@ -32,12 +25,11 @@ setup_translation()
@pytest.mark.gtkui
-class FilesTabTestCase(BaseTestCase):
+class TestFilesTab(BaseTestCase):
def set_up(self):
if libs_available is False:
- raise unittest.SkipTest('GTKUI dependencies not available')
+ pytest.skip('GTKUI dependencies not available')
- common.set_tmp_config_dir()
ConfigManager('gtk3ui.conf', defaults=DEFAULT_PREFS)
self.mainwindow = MainWindow()
self.filestab = FilesTab()
@@ -52,8 +44,8 @@ class FilesTabTestCase(BaseTestCase):
root = treestore.get_iter_first()
level = 1
- def p_level(s, l):
- print('%s%s' % (' ' * l, s))
+ def p_level(s, lvl):
+ print('{}{}'.format(' ' * lvl, s))
def _print_treestore_children(i, lvl):
while i:
@@ -98,80 +90,74 @@ class FilesTabTestCase(BaseTestCase):
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
- self.assertTrue(ret)
+ assert ret
def test_files_tab2(self):
- if windows_check():
- raise unittest.SkipTest('on windows \\ != / for path names')
self.filestab.files_list[self.t_id] = (
- {'index': 0, 'path': '1/1/test_10.txt', 'offset': 0, 'size': 13},
- {'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
+ {'index': 0, 'path': '1/1/test_100.txt', 'offset': 0, 'size': 13},
+ {'index': 1, 'path': 'test_101.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(
- self.t_id, self.index, '1/1/test_100.txt'
+ self.t_id, self.index, '1/1/test_101.txt'
)
ret = self.verify_treestore(
self.filestab.treestore,
- [['1/', [['1/', [['test_100.txt'], ['test_10.txt']]]]]],
+ [['1/', [['1/', [['test_100.txt'], ['test_101.txt']]]]]],
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
- self.assertTrue(ret)
+ assert ret
def test_files_tab3(self):
- if windows_check():
- raise unittest.SkipTest('on windows \\ != / for path names')
self.filestab.files_list[self.t_id] = (
- {'index': 0, 'path': '1/test_10.txt', 'offset': 0, 'size': 13},
- {'index': 1, 'path': 'test_100.txt', 'offset': 13, 'size': 14},
+ {'index': 0, 'path': '1/test_100.txt', 'offset': 0, 'size': 13},
+ {'index': 1, 'path': 'test_101.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(
- self.t_id, self.index, '1/test_100.txt'
+ self.t_id, self.index, '1/test_101.txt'
)
ret = self.verify_treestore(
- self.filestab.treestore, [['1/', [['test_100.txt'], ['test_10.txt']]]]
+ self.filestab.treestore, [['1/', [['test_100.txt'], ['test_101.txt']]]]
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
- self.assertTrue(ret)
+ assert ret
def test_files_tab4(self):
self.filestab.files_list[self.t_id] = (
- {'index': 0, 'path': '1/test_10.txt', 'offset': 0, 'size': 13},
- {'index': 1, 'path': '1/test_100.txt', 'offset': 13, 'size': 14},
+ {'index': 0, 'path': '1/test_100.txt', 'offset': 0, 'size': 13},
+ {'index': 1, 'path': '1/test_101.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(
- self.t_id, self.index, '1/2/test_100.txt'
+ self.t_id, self.index, '1/2/test_101.txt'
)
ret = self.verify_treestore(
self.filestab.treestore,
- [['1/', [['2/', [['test_100.txt']]], ['test_10.txt']]]],
+ [['1/', [['2/', [['test_101.txt']]], ['test_100.txt']]]],
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
- self.assertTrue(ret)
+ assert ret
def test_files_tab5(self):
- if windows_check():
- raise unittest.SkipTest('on windows \\ != / for path names')
self.filestab.files_list[self.t_id] = (
- {'index': 0, 'path': '1/test_10.txt', 'offset': 0, 'size': 13},
- {'index': 1, 'path': '2/test_100.txt', 'offset': 13, 'size': 14},
+ {'index': 0, 'path': '1/test_100.txt', 'offset': 0, 'size': 13},
+ {'index': 1, 'path': '2/test_101.txt', 'offset': 13, 'size': 14},
)
self.filestab.update_files()
self.filestab._on_torrentfilerenamed_event(
- self.t_id, self.index, '1/test_100.txt'
+ self.t_id, self.index, '1/test_101.txt'
)
ret = self.verify_treestore(
- self.filestab.treestore, [['1/', [['test_100.txt'], ['test_10.txt']]]]
+ self.filestab.treestore, [['1/', [['test_100.txt'], ['test_101.txt']]]]
)
if not ret:
self.print_treestore('Treestore not expected:', self.filestab.treestore)
- self.assertTrue(ret)
+ assert ret
diff --git a/deluge/tests/test_httpdownloader.py b/deluge/tests/test_httpdownloader.py
index ad947a422..8c491b68a 100644
--- a/deluge/tests/test_httpdownloader.py
+++ b/deluge/tests/test_httpdownloader.py
@@ -1,22 +1,18 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os
import tempfile
from email.utils import formatdate
-from io import open
+import pytest
+import pytest_twisted
from twisted.internet import reactor
from twisted.internet.error import CannotListenError
-from twisted.python.failure import Failure
-from twisted.trial import unittest
-from twisted.web.error import PageRedirect
+from twisted.web.error import Error, PageRedirect
from twisted.web.http import NOT_MODIFIED
from twisted.web.resource import EncodingResourceWrapper, Resource
from twisted.web.server import GzipEncoderFactory, Site
@@ -71,7 +67,7 @@ class TorrentResource(Resource):
content_type += b'; charset=' + charset
request.setHeader(b'Content-Type', content_type)
request.setHeader(b'Content-Disposition', b'attachment; filename=test.torrent')
- return 'Binary attachment ignore charset 世丕且\n'.encode('utf8')
+ return 'Binary attachment ignore charset 世丕且\n'.encode()
class CookieResource(Resource):
@@ -138,11 +134,13 @@ class TopLevelResource(Resource):
return b'<h1>Deluge HTTP Downloader tests webserver here</h1>'
-class DownloadFileTestCase(unittest.TestCase):
+class TestDownloadFile:
def get_url(self, path=''):
return 'http://localhost:%d/%s' % (self.listen_port, path)
- def setUp(self): # NOQA
+ @pytest_twisted.async_yield_fixture(autouse=True)
+ async def setUp(self, request): # NOQA
+ self = request.instance
setup_logger('warning', fname('log_file'))
self.website = Site(TopLevelResource())
self.listen_port = 51242
@@ -158,140 +156,136 @@ class DownloadFileTestCase(unittest.TestCase):
else:
raise error
- def tearDown(self): # NOQA
- return self.webserver.stopListening()
+ yield
+
+ await self.webserver.stopListening()
- def assertContains(self, filename, contents): # NOQA
- with open(filename, 'r', encoding='utf8') as _file:
+ def assert_contains(self, filename, contents):
+ with open(filename, encoding='utf8') as _file:
try:
- self.assertEqual(_file.read(), contents)
+ assert _file.read() == contents
except Exception as ex:
- self.fail(ex)
+ pytest.fail(ex)
return filename
- def assertNotContains(self, filename, contents, file_mode=''): # NOQA
- with open(filename, 'r', encoding='utf8') as _file:
+ def assert_not_contains(self, filename, contents, file_mode=''):
+ with open(filename, encoding='utf8') as _file:
try:
- self.assertNotEqual(_file.read(), contents)
+ assert _file.read() != contents
except Exception as ex:
- self.fail(ex)
+ pytest.fail(ex)
return filename
- def test_download(self):
- d = download_file(self.get_url(), fname('index.html'))
- d.addCallback(self.assertEqual, fname('index.html'))
- return d
+ @pytest_twisted.ensureDeferred
+ async def test_download(self):
+ filename = await download_file(self.get_url(), fname('index.html'))
+ assert filename == fname('index.html')
- def test_download_without_required_cookies(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_without_required_cookies(self):
url = self.get_url('cookie')
- d = download_file(url, fname('none'))
- d.addCallback(self.fail)
- d.addErrback(self.assertIsInstance, Failure)
- return d
+ filename = await download_file(url, fname('none'))
+ self.assert_contains(filename, 'Password cookie not set!')
- def test_download_with_required_cookies(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_required_cookies(self):
url = self.get_url('cookie')
cookie = {'cookie': 'password=deluge'}
- d = download_file(url, fname('monster'), headers=cookie)
- d.addCallback(self.assertEqual, fname('monster'))
- d.addCallback(self.assertContains, 'COOKIE MONSTER!')
- return d
+ filename = await download_file(url, fname('monster'), headers=cookie)
+ assert filename == fname('monster')
+ self.assert_contains(filename, 'COOKIE MONSTER!')
- def test_download_with_rename(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_rename(self):
url = self.get_url('rename?filename=renamed')
- d = download_file(url, fname('original'))
- d.addCallback(self.assertEqual, fname('renamed'))
- d.addCallback(self.assertContains, 'This file should be called renamed')
- return d
+ filename = await download_file(url, fname('original'))
+ assert filename == fname('renamed')
+ self.assert_contains(filename, 'This file should be called renamed')
- def test_download_with_rename_exists(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_rename_exists(self):
open(fname('renamed'), 'w').close()
url = self.get_url('rename?filename=renamed')
- d = download_file(url, fname('original'))
- d.addCallback(self.assertEqual, fname('renamed-1'))
- d.addCallback(self.assertContains, 'This file should be called renamed')
- return d
+ filename = await download_file(url, fname('original'))
+ assert filename == fname('renamed-1')
+ self.assert_contains(filename, 'This file should be called renamed')
- def test_download_with_rename_sanitised(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_rename_sanitised(self):
url = self.get_url('rename?filename=/etc/passwd')
- d = download_file(url, fname('original'))
- d.addCallback(self.assertEqual, fname('passwd'))
- d.addCallback(self.assertContains, 'This file should be called /etc/passwd')
- return d
+ filename = await download_file(url, fname('original'))
+ assert filename == fname('passwd')
+ self.assert_contains(filename, 'This file should be called /etc/passwd')
- def test_download_with_attachment_no_filename(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_attachment_no_filename(self):
url = self.get_url('attachment')
- d = download_file(url, fname('original'))
- d.addCallback(self.assertEqual, fname('original'))
- d.addCallback(self.assertContains, 'Attachment with no filename set')
- return d
+ filename = await download_file(url, fname('original'))
+ assert filename == fname('original')
+ self.assert_contains(filename, 'Attachment with no filename set')
- def test_download_with_rename_prevented(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_rename_prevented(self):
url = self.get_url('rename?filename=spam')
- d = download_file(url, fname('forced'), force_filename=True)
- d.addCallback(self.assertEqual, fname('forced'))
- d.addCallback(self.assertContains, 'This file should be called spam')
- return d
+ filename = await download_file(url, fname('forced'), force_filename=True)
+ assert filename == fname('forced')
+ self.assert_contains(filename, 'This file should be called spam')
- def test_download_with_gzip_encoding(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_gzip_encoding(self):
url = self.get_url('gzip?msg=success')
- d = download_file(url, fname('gzip_encoded'))
- d.addCallback(self.assertContains, 'success')
- return d
+ filename = await download_file(url, fname('gzip_encoded'))
+ self.assert_contains(filename, 'success')
- def test_download_with_gzip_encoding_disabled(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_with_gzip_encoding_disabled(self):
url = self.get_url('gzip?msg=unzip')
- d = download_file(url, fname('gzip_encoded'), allow_compression=False)
- d.addCallback(self.assertContains, 'unzip')
- return d
+ filename = await download_file(
+ url, fname('gzip_encoded'), allow_compression=False
+ )
+ self.assert_contains(filename, 'unzip')
- def test_page_redirect_unhandled(self):
+ @pytest_twisted.ensureDeferred
+ async def test_page_redirect_unhandled(self):
url = self.get_url('redirect')
- d = download_file(url, fname('none'))
- d.addCallback(self.fail)
+ with pytest.raises(PageRedirect):
+ await download_file(url, fname('none'), handle_redirects=False)
- def on_redirect(failure):
- self.assertTrue(type(failure), PageRedirect)
+ @pytest_twisted.ensureDeferred
+ async def test_page_redirect(self):
+ url = self.get_url('redirect')
+ filename = await download_file(url, fname('none'), handle_redirects=True)
+ assert filename == fname('none')
- d.addErrback(on_redirect)
- return d
+ @pytest_twisted.ensureDeferred
+ async def test_page_not_found(self):
+ with pytest.raises(Error):
+ await download_file(self.get_url('page/not/found'), fname('none'))
- def test_page_redirect(self):
- url = self.get_url('redirect')
- d = download_file(url, fname('none'), handle_redirects=True)
- d.addCallback(self.assertEqual, fname('none'))
- d.addErrback(self.fail)
- return d
-
- def test_page_not_found(self):
- d = download_file(self.get_url('page/not/found'), fname('none'))
- d.addCallback(self.fail)
- d.addErrback(self.assertIsInstance, Failure)
- return d
-
- def test_page_not_modified(self):
+ @pytest.mark.xfail(reason="Doesn't seem like httpdownloader ever implemented this.")
+ @pytest_twisted.ensureDeferred
+ async def test_page_not_modified(self):
headers = {'If-Modified-Since': formatdate(usegmt=True)}
- d = download_file(self.get_url(), fname('index.html'), headers=headers)
- d.addCallback(self.fail)
- d.addErrback(self.assertIsInstance, Failure)
- return d
+ with pytest.raises(Error) as exc_info:
+ await download_file(self.get_url(), fname('index.html'), headers=headers)
+ assert exc_info.value.status == NOT_MODIFIED
- def test_download_text_reencode_charset(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_text_reencode_charset(self):
"""Re-encode as UTF-8 specified charset for text content-type header"""
url = self.get_url('attachment')
filepath = fname('test.txt')
headers = {'content-charset': 'Windows-1251', 'content-append': 'бвгде'}
- d = download_file(url, filepath, headers=headers)
- d.addCallback(self.assertEqual, filepath)
- d.addCallback(self.assertContains, 'Attachment with no filename setбвгде')
- return d
+ filename = await download_file(url, filepath, headers=headers)
+ assert filename == filepath
+ self.assert_contains(filename, 'Attachment with no filename setбвгде')
- def test_download_binary_ignore_charset(self):
+ @pytest_twisted.ensureDeferred
+ async def test_download_binary_ignore_charset(self):
"""Ignore charset for binary content-type header e.g. torrent files"""
url = self.get_url('torrent')
headers = {'content-charset': 'Windows-1251'}
filepath = fname('test.torrent')
- d = download_file(url, fname('test.torrent'), headers=headers)
- d.addCallback(self.assertEqual, filepath)
- d.addCallback(self.assertContains, 'Binary attachment ignore charset 世丕且\n')
- return d
+ filename = await download_file(url, fname('test.torrent'), headers=headers)
+ assert filename == filepath
+ self.assert_contains(filename, 'Binary attachment ignore charset 世丕且\n')
diff --git a/deluge/tests/test_json_api.py b/deluge/tests/test_json_api.py
index 1da64bf97..41efb0206 100644
--- a/deluge/tests/test_json_api.py
+++ b/deluge/tests/test_json_api.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,64 +6,35 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import json as json_lib
+from unittest.mock import MagicMock
-from mock import MagicMock
-from twisted.internet import defer
+import pytest
+import pytest_twisted
from twisted.web import server
from twisted.web.http import Request
import deluge.common
-import deluge.component as component
import deluge.ui.web.auth
import deluge.ui.web.json_api
from deluge.error import DelugeError
-from deluge.ui.client import client
from deluge.ui.web.auth import Auth
from deluge.ui.web.json_api import JSON, JSONException
from . import common
-from .basetest import BaseTestCase
from .common_web import WebServerMockBase
-from .daemon_base import DaemonBase
common.disable_new_release_check()
-class JSONBase(BaseTestCase, DaemonBase):
- def connect_client(self, *args, **kwargs):
- return client.connect(
- 'localhost',
- self.listen_port,
- username=kwargs.get('user', ''),
- password=kwargs.get('password', ''),
- )
-
- def disconnect_client(self, *args):
- return client.disconnect()
-
- def tear_down(self):
- d = component.shutdown()
- d.addCallback(self.disconnect_client)
- d.addCallback(self.terminate_core)
- return d
-
-
-class JSONTestCase(JSONBase):
- def set_up(self):
- d = self.common_set_up()
- d.addCallback(self.start_core)
- d.addCallbacks(self.connect_client, self.terminate_core)
- return d
-
- @defer.inlineCallbacks
- def test_get_remote_methods(self):
+@pytest.mark.usefixtures('daemon', 'client', 'component')
+class TestJSON:
+ @pytest_twisted.ensureDeferred
+ async def test_get_remote_methods(self):
json = JSON()
- methods = yield json.get_remote_methods()
- self.assertEqual(type(methods), tuple)
- self.assertTrue(len(methods) > 0)
+ methods = await json.get_remote_methods()
+ assert type(methods) == tuple
+ assert len(methods) > 0
def test_render_fail_disconnected(self):
json = JSON()
@@ -72,7 +42,7 @@ class JSONTestCase(JSONBase):
request.method = b'POST'
request._disconnected = True
# When disconnected, returns empty string
- self.assertEqual(json.render(request), '')
+ assert json.render(request) == ''
def test_render_fail(self):
json = JSON()
@@ -82,19 +52,17 @@ class JSONTestCase(JSONBase):
def write(response_str):
request.write_was_called = True
response = json_lib.loads(response_str.decode())
- self.assertEqual(response['result'], None)
- self.assertEqual(response['id'], None)
- self.assertEqual(
- response['error']['message'], 'JSONException: JSON not decodable'
- )
- self.assertEqual(response['error']['code'], 5)
+ assert response['result'] is None
+ assert response['id'] is None
+ assert response['error']['message'] == 'JSONException: JSON not decodable'
+ assert response['error']['code'] == 5
request.write = write
request.write_was_called = False
request._disconnected = False
request.getHeader.return_value = b'application/json'
- self.assertEqual(json.render(request), server.NOT_DONE_YET)
- self.assertTrue(request.write_was_called)
+ assert json.render(request) == server.NOT_DONE_YET
+ assert request.write_was_called
def test_handle_request_invalid_method(self):
json = JSON()
@@ -102,20 +70,23 @@ class JSONTestCase(JSONBase):
json_data = {'method': 'no-existing-module.test', 'id': 0, 'params': []}
request.json = json_lib.dumps(json_data).encode()
request_id, result, error = json._handle_request(request)
- self.assertEqual(error, {'message': 'Unknown method', 'code': 2})
+ assert error == {'message': 'Unknown method', 'code': 2}
def test_handle_request_invalid_json_request(self):
json = JSON()
request = MagicMock()
json_data = {'id': 0, 'params': []}
request.json = json_lib.dumps(json_data).encode()
- self.assertRaises(JSONException, json._handle_request, request)
+ with pytest.raises(JSONException):
+ json._handle_request(request)
json_data = {'method': 'some.method', 'params': []}
request.json = json_lib.dumps(json_data).encode()
- self.assertRaises(JSONException, json._handle_request, request)
+ with pytest.raises(JSONException):
+ json._handle_request(request)
json_data = {'method': 'some.method', 'id': 0}
request.json = json_lib.dumps(json_data).encode()
- self.assertRaises(JSONException, json._handle_request, request)
+ with pytest.raises(JSONException):
+ json._handle_request(request)
def test_on_json_request_invalid_content_type(self):
"""Test for exception with content type not application/json"""
@@ -124,18 +95,32 @@ class JSONTestCase(JSONBase):
request.getHeader.return_value = b'text/plain'
json_data = {'method': 'some.method', 'id': 0, 'params': []}
request.json = json_lib.dumps(json_data).encode()
- self.assertRaises(JSONException, json._on_json_request, request)
+ with pytest.raises(JSONException):
+ json._on_json_request(request)
+ def test_on_json_request_valid_content_type(self):
+ """Ensure content-type application/json is accepted"""
+ json = JSON()
+ request = MagicMock()
+ request.getHeader.return_value = b'application/json'
+ json_data = {'method': 'some.method', 'id': 0, 'params': []}
+ request.json = json_lib.dumps(json_data).encode()
+ json._on_json_request(request)
-class JSONCustomUserTestCase(JSONBase):
- def set_up(self):
- d = self.common_set_up()
- d.addCallback(self.start_core)
- return d
+ def test_on_json_request_valid_content_type_with_charset(self):
+ """Ensure content-type parameters such as charset are ignored"""
+ json = JSON()
+ request = MagicMock()
+ request.getHeader.return_value = b'application/json;charset=utf-8'
+ json_data = {'method': 'some.method', 'id': 0, 'params': []}
+ request.json = json_lib.dumps(json_data).encode()
+ json._on_json_request(request)
- @defer.inlineCallbacks
+
+@pytest.mark.usefixtures('daemon', 'client', 'component')
+class TestJSONCustomUserTestCase:
+ @pytest_twisted.inlineCallbacks
def test_handle_request_auth_error(self):
- yield self.connect_client()
json = JSON()
auth_conf = {'session_timeout': 10, 'sessions': {}}
Auth(auth_conf) # Must create the component
@@ -148,13 +133,12 @@ class JSONCustomUserTestCase(JSONBase):
json_data = {'method': 'core.get_libtorrent_version', 'id': 0, 'params': []}
request.json = json_lib.dumps(json_data).encode()
request_id, result, error = json._handle_request(request)
- self.assertEqual(error, {'message': 'Not authenticated', 'code': 1})
+ assert error == {'message': 'Not authenticated', 'code': 1}
-class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
- def set_up(self):
- d = self.common_set_up()
- custom_script = """
+@pytest.mark.usefixtures('daemon', 'client', 'component')
+class TestRPCRaiseDelugeErrorJSON:
+ daemon_custom_script = """
from deluge.error import DelugeError
from deluge.core.rpcserver import export
class TestClass(object):
@@ -165,12 +149,9 @@ class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
test = TestClass()
daemon.rpcserver.register_object(test)
"""
- d.addCallback(self.start_core, custom_script=custom_script)
- d.addCallbacks(self.connect_client, self.terminate_core)
- return d
- @defer.inlineCallbacks
- def test_handle_request_method_raise_delugeerror(self):
+ @pytest_twisted.ensureDeferred
+ async def test_handle_request_method_raise_delugeerror(self):
json = JSON()
def get_session_id(s_id):
@@ -182,9 +163,9 @@ class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
request = Request(MagicMock(), False)
request.base = b''
auth._create_session(request)
- methods = yield json.get_remote_methods()
+ methods = await json.get_remote_methods()
# Verify the function has been registered
- self.assertTrue('testclass.test' in methods)
+ assert 'testclass.test' in methods
request = MagicMock()
session_id = list(auth.config['sessions'])[0]
@@ -192,18 +173,13 @@ class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
json_data = {'method': 'testclass.test', 'id': 0, 'params': []}
request.json = json_lib.dumps(json_data).encode()
request_id, result, error = json._handle_request(request)
- result.addCallback(self.fail)
-
- def on_error(error):
- self.assertEqual(error.type, DelugeError)
-
- result.addErrback(on_error)
- yield result
+ with pytest.raises(DelugeError):
+ await result
-class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
- def set_up(self):
- d = self.common_set_up()
+class TestJSONRequestFailed(WebServerMockBase):
+ @pytest_twisted.async_yield_fixture(autouse=True)
+ async def set_up(self, config_dir):
custom_script = """
from deluge.error import DelugeError
from deluge.core.rpcserver import export
@@ -234,28 +210,29 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
}
def on_test_raise(*args):
- self.assertTrue('Unhandled error in Deferred:' in self.core.stderr_out)
- self.assertTrue('in test_raise_error' in self.core.stderr_out)
+ assert 'Unhandled error in Deferred:' in self.core.stderr_out
+ assert 'in test_raise_error' in self.core.stderr_out
extra_callback['deferred'].addCallback(on_test_raise)
- d.addCallback(
- self.start_core,
+ d, daemon = common.start_core(
custom_script=custom_script,
print_stdout=False,
print_stderr=False,
timeout=5,
extra_callbacks=[extra_callback],
+ config_directory=config_dir,
)
- d.addCallbacks(self.connect_client, self.terminate_core)
- return d
+ await d
+ yield
+ await daemon.kill()
- @defer.inlineCallbacks
- def test_render_on_rpc_request_failed(self):
+ @pytest_twisted.inlineCallbacks
+ def test_render_on_rpc_request_failed(self, component, client):
json = JSON()
methods = yield json.get_remote_methods()
# Verify the function has been registered
- self.assertTrue('testclass.test' in methods)
+ assert 'testclass.test' in methods
request = MagicMock()
@@ -266,14 +243,14 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
def write(response_str):
request.write_was_called = True
response = json_lib.loads(response_str.decode())
- self.assertEqual(response['result'], None, 'BAD RESULT')
- self.assertEqual(response['id'], 0)
- self.assertEqual(
- response['error']['message'],
- 'Failure: [Failure instance: Traceback (failure with no frames):'
- " <class 'deluge.error.DelugeError'>: DelugeERROR\n]",
+ assert response['result'] is None, 'BAD RESULT'
+ assert response['id'] == 0
+ assert (
+ response['error']['message']
+ == 'Failure: [Failure instance: Traceback (failure with no frames):'
+ " <class 'deluge.error.DelugeError'>: DelugeERROR\n]"
)
- self.assertEqual(response['error']['code'], 4)
+ assert response['error']['code'] == 4
request.write = write
request.write_was_called = False
@@ -284,8 +261,8 @@ class JSONRequestFailedTestCase(JSONBase, WebServerMockBase):
d = json._on_json_request(request)
def on_success(arg):
- self.assertEqual(arg, server.NOT_DONE_YET)
+ assert arg == server.NOT_DONE_YET
return True
- d.addCallbacks(on_success, self.fail)
+ d.addCallbacks(on_success, pytest.fail)
yield d
diff --git a/deluge/tests/test_log.py b/deluge/tests/test_log.py
index 572693b7c..f0dcbee86 100644
--- a/deluge/tests/test_log.py
+++ b/deluge/tests/test_log.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 Calum Lind <calumlind@gmail.com>
# Copyright (C) 2010 Pedro Algarvio <ufs@ufsoft.org>
@@ -8,17 +7,14 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import warnings
+from deluge.conftest import BaseTestCase
from deluge.log import setup_logger
-from .basetest import BaseTestCase
-
-class LogTestCase(BaseTestCase):
+class TestLog(BaseTestCase):
def set_up(self):
setup_logger(logging.DEBUG)
@@ -32,7 +28,7 @@ class LogTestCase(BaseTestCase):
# Cause all warnings to always be triggered.
warnings.simplefilter('always')
LOG.debug('foo')
- self.assertEqual(w[-1].category, DeprecationWarning)
+ assert w[-1].category == DeprecationWarning
# def test_twisted_error_log(self):
# from twisted.internet import defer
diff --git a/deluge/tests/test_maketorrent.py b/deluge/tests/test_maketorrent.py
index 4e0099653..a2e473f00 100644
--- a/deluge/tests/test_maketorrent.py
+++ b/deluge/tests/test_maketorrent.py
@@ -1,19 +1,13 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os
import tempfile
-from twisted.trial import unittest
-
from deluge import maketorrent
-from deluge.common import windows_check
def check_torrent(filename):
@@ -28,7 +22,7 @@ def check_torrent(filename):
TorrentInfo(filename)
-class MakeTorrentTestCase(unittest.TestCase):
+class TestMakeTorrent:
def test_save_multifile(self):
# Create a temporary folder for torrent creation
tmp_path = tempfile.mkdtemp()
@@ -54,21 +48,16 @@ class MakeTorrentTestCase(unittest.TestCase):
os.remove(tmp_file)
def test_save_singlefile(self):
- if windows_check():
- raise unittest.SkipTest('on windows file not released')
- tmp_data = tempfile.mkstemp('testdata')[1]
- with open(tmp_data, 'wb') as _file:
- _file.write(b'a' * (2314 * 1024))
- t = maketorrent.TorrentMetadata()
- t.data_path = tmp_data
- tmp_fd, tmp_file = tempfile.mkstemp('.torrent')
- t.save(tmp_file)
-
- check_torrent(tmp_file)
-
- os.remove(tmp_data)
- os.close(tmp_fd)
- os.remove(tmp_file)
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ tmp_data = tmp_dir + '/data'
+ with open(tmp_data, 'wb') as _file:
+ _file.write(b'a' * (2314 * 1024))
+ t = maketorrent.TorrentMetadata()
+ t.data_path = tmp_data
+ tmp_file = tmp_dir + '/.torrent'
+ t.save(tmp_file)
+
+ check_torrent(tmp_file)
def test_save_multifile_padded(self):
# Create a temporary folder for torrent creation
diff --git a/deluge/tests/test_maybe_coroutine.py b/deluge/tests/test_maybe_coroutine.py
new file mode 100644
index 000000000..2717e78bb
--- /dev/null
+++ b/deluge/tests/test_maybe_coroutine.py
@@ -0,0 +1,213 @@
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+import pytest
+import pytest_twisted
+import twisted.python.failure
+from twisted.internet import defer, reactor, task
+from twisted.internet.defer import maybeDeferred
+
+from deluge.decorators import maybe_coroutine
+
+
+@defer.inlineCallbacks
+def inline_func():
+ result = yield task.deferLater(reactor, 0, lambda: 'function_result')
+ return result
+
+
+@defer.inlineCallbacks
+def inline_error():
+ raise Exception('function_error')
+ yield
+
+
+@maybe_coroutine
+async def coro_func():
+ result = await task.deferLater(reactor, 0, lambda: 'function_result')
+ return result
+
+
+@maybe_coroutine
+async def coro_error():
+ raise Exception('function_error')
+
+
+@defer.inlineCallbacks
+def coro_func_from_inline():
+ result = yield coro_func()
+ return result
+
+
+@defer.inlineCallbacks
+def coro_error_from_inline():
+ result = yield coro_error()
+ return result
+
+
+@maybe_coroutine
+async def coro_func_from_coro():
+ return await coro_func()
+
+
+@maybe_coroutine
+async def coro_error_from_coro():
+ return await coro_error()
+
+
+@maybe_coroutine
+async def inline_func_from_coro():
+ return await inline_func()
+
+
+@maybe_coroutine
+async def inline_error_from_coro():
+ return await inline_error()
+
+
+@pytest_twisted.inlineCallbacks
+def test_standard_twisted():
+ """Sanity check that twisted tests work how we expect.
+
+ Not really testing deluge code at all.
+ """
+ result = yield inline_func()
+ assert result == 'function_result'
+
+ with pytest.raises(Exception, match='function_error'):
+ yield inline_error()
+
+
+@pytest.mark.parametrize(
+ 'function',
+ [
+ inline_func,
+ coro_func,
+ coro_func_from_coro,
+ coro_func_from_inline,
+ inline_func_from_coro,
+ ],
+)
+@pytest_twisted.inlineCallbacks
+def test_from_inline(function):
+ """Test our coroutines wrapped with maybe_coroutine as if they returned plain twisted deferreds."""
+ result = yield function()
+ assert result == 'function_result'
+
+ def cb(result):
+ assert result == 'function_result'
+
+ d = function()
+ d.addCallback(cb)
+ yield d
+
+
+@pytest.mark.parametrize(
+ 'function',
+ [
+ inline_error,
+ coro_error,
+ coro_error_from_coro,
+ coro_error_from_inline,
+ inline_error_from_coro,
+ ],
+)
+@pytest_twisted.inlineCallbacks
+def test_error_from_inline(function):
+ """Test our coroutines wrapped with maybe_coroutine as if they returned plain twisted deferreds that raise."""
+ with pytest.raises(Exception, match='function_error'):
+ yield function()
+
+ def eb(result):
+ assert isinstance(result, twisted.python.failure.Failure)
+ assert result.getErrorMessage() == 'function_error'
+
+ d = function()
+ d.addErrback(eb)
+ yield d
+
+
+@pytest.mark.parametrize(
+ 'function',
+ [
+ inline_func,
+ coro_func,
+ coro_func_from_coro,
+ coro_func_from_inline,
+ inline_func_from_coro,
+ ],
+)
+@pytest_twisted.ensureDeferred
+async def test_from_coro(function):
+ """Test our coroutines wrapped with maybe_coroutine work from another coroutine."""
+ result = await function()
+ assert result == 'function_result'
+
+
+@pytest.mark.parametrize(
+ 'function',
+ [
+ inline_error,
+ coro_error,
+ coro_error_from_coro,
+ coro_error_from_inline,
+ inline_error_from_coro,
+ ],
+)
+@pytest_twisted.ensureDeferred
+async def test_error_from_coro(function):
+ """Test our coroutines wrapped with maybe_coroutine work from another coroutine with errors."""
+ with pytest.raises(Exception, match='function_error'):
+ await function()
+
+
+@pytest_twisted.ensureDeferred
+async def test_tracebacks_preserved():
+ with pytest.raises(Exception) as exc:
+ await coro_error_from_coro()
+ traceback_lines = [
+ 'await coro_error_from_coro()',
+ 'return await coro_error()',
+ "raise Exception('function_error')",
+ ]
+ # If each coroutine got wrapped with ensureDeferred, the traceback will be mangled
+ # verify the coroutines passed through by checking the traceback.
+ for expected, actual in zip(traceback_lines, exc.traceback):
+ assert expected in str(actual)
+
+
+@pytest_twisted.ensureDeferred
+async def test_maybe_deferred_coroutine():
+ result = await maybeDeferred(coro_func)
+ assert result == 'function_result'
+
+
+@pytest_twisted.ensureDeferred
+async def test_callback_before_await():
+ def cb(res):
+ assert res == 'function_result'
+ return res
+
+ d = coro_func()
+ d.addCallback(cb)
+ result = await d
+ assert result == 'function_result'
+
+
+@pytest_twisted.ensureDeferred
+async def test_callback_after_await():
+ """If it has already been used as a coroutine, can't be retroactively turned into a Deferred.
+ This limitation could be fixed, but the extra complication doesn't feel worth it.
+ """
+
+ def cb(res):
+ pass
+
+ d = coro_func()
+ await d
+ with pytest.raises(
+ Exception, match='Cannot add callbacks to an already awaited coroutine'
+ ):
+ d.addCallback(cb)
diff --git a/deluge/tests/test_metafile.py b/deluge/tests/test_metafile.py
index fc6507cb8..fda1cb73e 100644
--- a/deluge/tests/test_metafile.py
+++ b/deluge/tests/test_metafile.py
@@ -1,19 +1,13 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os
import tempfile
-from twisted.trial import unittest
-
from deluge import metafile
-from deluge.common import windows_check
def check_torrent(filename):
@@ -28,7 +22,7 @@ def check_torrent(filename):
TorrentInfo(filename)
-class MetafileTestCase(unittest.TestCase):
+class TestMetafile:
def test_save_multifile(self):
# Create a temporary folder for torrent creation
tmp_path = tempfile.mkdtemp()
@@ -52,17 +46,12 @@ class MetafileTestCase(unittest.TestCase):
os.remove(tmp_file)
def test_save_singlefile(self):
- if windows_check():
- raise unittest.SkipTest('on windows \\ != / for path names')
- tmp_path = tempfile.mkstemp('testdata')[1]
- with open(tmp_path, 'wb') as tmp_file:
- tmp_file.write(b'a' * (2314 * 1024))
-
- tmp_fd, tmp_file = tempfile.mkstemp('.torrent')
- metafile.make_meta_file(tmp_path, '', 32768, target=tmp_file)
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ tmp_data = tmp_dir + '/testdata'
+ with open(tmp_data, 'wb') as tmp_file:
+ tmp_file.write(b'a' * (2314 * 1024))
- check_torrent(tmp_file)
+ tmp_torrent = tmp_dir + '/.torrent'
+ metafile.make_meta_file(tmp_data, '', 32768, target=tmp_torrent)
- os.remove(tmp_path)
- os.close(tmp_fd)
- os.remove(tmp_file)
+ check_torrent(tmp_torrent)
diff --git a/deluge/tests/test_plugin_metadata.py b/deluge/tests/test_plugin_metadata.py
index 436fc2c50..adf115d1b 100644
--- a/deluge/tests/test_plugin_metadata.py
+++ b/deluge/tests/test_plugin_metadata.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 Calum Lind <calumlind@gmail.com>
#
@@ -7,25 +6,38 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.pluginmanagerbase import PluginManagerBase
-from . import common
-from .basetest import BaseTestCase
-
-
-class PluginManagerBaseTestCase(BaseTestCase):
- def set_up(self):
- common.set_tmp_config_dir()
+class TestPluginManagerBase:
def test_get_plugin_info(self):
pm = PluginManagerBase('core.conf', 'deluge.plugin.core')
for p in pm.get_available_plugins():
for key, value in pm.get_plugin_info(p).items():
- self.assertTrue(isinstance('%s: %s' % (key, value), ''.__class__))
+ assert isinstance(key, str)
+ assert isinstance(value, str)
def test_get_plugin_info_invalid_name(self):
pm = PluginManagerBase('core.conf', 'deluge.plugin.core')
for key, value in pm.get_plugin_info('random').items():
- self.assertEqual(value, 'not available')
+ result = 'not available' if key in ('Name', 'Version') else ''
+ assert value == result
+
+ def test_parse_pkg_info_metadata_2_1(self):
+ pkg_info = """Metadata-Version: 2.1
+Name: AutoAdd
+Version: 1.8
+Summary: Monitors folders for .torrent files.
+Home-page: http://dev.deluge-torrent.org/wiki/Plugins/AutoAdd
+Author: Chase Sterling, Pedro Algarvio
+Author-email: chase.sterling@gmail.com, pedro@algarvio.me
+License: GPLv3
+Platform: UNKNOWN
+
+Monitors folders for .torrent files.
+ """
+ plugin_info = PluginManagerBase.parse_pkg_info(pkg_info)
+ for value in plugin_info.values():
+ assert value != ''
+ result = 'Monitors folders for .torrent files.'
+ assert plugin_info['Description'] == result
diff --git a/deluge/tests/test_rpcserver.py b/deluge/tests/test_rpcserver.py
index 02f9af023..982d1d5f1 100644
--- a/deluge/tests/test_rpcserver.py
+++ b/deluge/tests/test_rpcserver.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Bro <bro.development@gmail.com>
#
@@ -7,18 +6,15 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
import deluge.error
from deluge.common import get_localhost_auth
+from deluge.conftest import BaseTestCase
from deluge.core import rpcserver
from deluge.core.authmanager import AuthManager
from deluge.core.rpcserver import DelugeRPCProtocol, RPCServer
from deluge.log import setup_logger
-from .basetest import BaseTestCase
-
setup_logger('none')
@@ -30,7 +26,7 @@ class DelugeRPCProtocolTester(DelugeRPCProtocol):
self.messages.append(data)
-class RPCServerTestCase(BaseTestCase):
+class TestRPCServer(BaseTestCase):
def set_up(self):
self.rpcserver = RPCServer(listen=False)
self.rpcserver.factory.protocol = DelugeRPCProtocolTester
@@ -60,15 +56,15 @@ class RPCServerTestCase(BaseTestCase):
e = TorrentFolderRenamedEvent(*data)
self.rpcserver.emit_event_for_session_id(self.session_id, e)
msg = self.protocol.messages.pop()
- self.assertEqual(msg[0], rpcserver.RPC_EVENT, str(msg))
- self.assertEqual(msg[1], 'TorrentFolderRenamedEvent', str(msg))
- self.assertEqual(msg[2], data, str(msg))
+ assert msg[0] == rpcserver.RPC_EVENT, str(msg)
+ assert msg[1] == 'TorrentFolderRenamedEvent', str(msg)
+ assert msg[2] == data, str(msg)
def test_invalid_client_login(self):
self.protocol.dispatch(self.request_id, 'daemon.login', [1], {})
msg = self.protocol.messages.pop()
- self.assertEqual(msg[0], rpcserver.RPC_ERROR)
- self.assertEqual(msg[1], self.request_id)
+ assert msg[0] == rpcserver.RPC_ERROR
+ assert msg[1] == self.request_id
def test_valid_client_login(self):
self.authmanager = AuthManager()
@@ -77,9 +73,9 @@ class RPCServerTestCase(BaseTestCase):
self.request_id, 'daemon.login', auth, {'client_version': 'Test'}
)
msg = self.protocol.messages.pop()
- self.assertEqual(msg[0], rpcserver.RPC_RESPONSE, str(msg))
- self.assertEqual(msg[1], self.request_id, str(msg))
- self.assertEqual(msg[2], rpcserver.AUTH_LEVEL_ADMIN, str(msg))
+ assert msg[0] == rpcserver.RPC_RESPONSE, str(msg)
+ assert msg[1] == self.request_id, str(msg)
+ assert msg[2] == rpcserver.AUTH_LEVEL_ADMIN, str(msg)
def test_client_login_error(self):
# This test causes error log prints while running the test...
@@ -90,24 +86,24 @@ class RPCServerTestCase(BaseTestCase):
self.request_id, 'daemon.login', auth, {'client_version': 'Test'}
)
msg = self.protocol.messages.pop()
- self.assertEqual(msg[0], rpcserver.RPC_ERROR)
- self.assertEqual(msg[1], self.request_id)
- self.assertEqual(msg[2], 'WrappedException')
- self.assertEqual(msg[3][1], 'AttributeError')
+ assert msg[0] == rpcserver.RPC_ERROR
+ assert msg[1] == self.request_id
+ assert msg[2] == 'WrappedException'
+ assert msg[3][1] == 'AttributeError'
def test_client_invalid_method_call(self):
self.authmanager = AuthManager()
auth = get_localhost_auth()
self.protocol.dispatch(self.request_id, 'invalid_function', auth, {})
msg = self.protocol.messages.pop()
- self.assertEqual(msg[0], rpcserver.RPC_ERROR)
- self.assertEqual(msg[1], self.request_id)
- self.assertEqual(msg[2], 'WrappedException')
- self.assertEqual(msg[3][1], 'AttributeError')
+ assert msg[0] == rpcserver.RPC_ERROR
+ assert msg[1] == self.request_id
+ assert msg[2] == 'WrappedException'
+ assert msg[3][1] == 'AttributeError'
def test_daemon_info(self):
self.protocol.dispatch(self.request_id, 'daemon.info', [], {})
msg = self.protocol.messages.pop()
- self.assertEqual(msg[0], rpcserver.RPC_RESPONSE, str(msg))
- self.assertEqual(msg[1], self.request_id, str(msg))
- self.assertEqual(msg[2], deluge.common.get_version(), str(msg))
+ assert msg[0] == rpcserver.RPC_RESPONSE, str(msg)
+ assert msg[1] == self.request_id, str(msg)
+ assert msg[2] == deluge.common.get_version(), str(msg)
diff --git a/deluge/tests/test_security.py b/deluge/tests/test_security.py
index 700fc9967..e3e434433 100644
--- a/deluge/tests/test_security.py
+++ b/deluge/tests/test_security.py
@@ -1,12 +1,9 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import os
import pytest
@@ -16,9 +13,9 @@ import deluge.component as component
import deluge.ui.web.server
from deluge import configmanager
from deluge.common import windows_check
+from deluge.conftest import BaseTestCase
from deluge.ui.web.server import DelugeWeb
-from .basetest import BaseTestCase
from .common import get_test_data_file
from .common_web import WebServerTestBase
from .daemon_base import DaemonBase
@@ -26,15 +23,10 @@ from .daemon_base import DaemonBase
SECURITY_TESTS = bool(os.getenv('SECURITY_TESTS', False))
-class SecurityBaseTestCase(object):
- if windows_check():
- skip = 'windows cannot run .sh files'
- elif not SECURITY_TESTS:
- skip = 'Skipping security tests'
-
- http_err = 'cannot run http tests on daemon'
-
- def __init__(self):
+# TODO: This whole module has not been tested since migrating tests fully to pytest
+class SecurityBaseTestCase:
+ @pytest.fixture(autouse=True)
+ def setvars(self):
self.home_dir = os.path.expanduser('~')
self.port = 8112
@@ -45,6 +37,7 @@ class SecurityBaseTestCase(object):
get_test_data_file('testssl.sh'),
'--quiet',
'--nodns',
+ 'none',
'--color',
'0',
test,
@@ -55,11 +48,11 @@ class SecurityBaseTestCase(object):
def on_result(results):
if test == '-e':
- results = results[0].split('\n')[7:-6]
- self.assertTrue(len(results) > 3)
+ results = results[0].split(b'\n')[7:-6]
+ assert len(results) > 3
else:
- self.assertIn('OK', results[0])
- self.assertNotIn('NOT ok', results[0])
+ assert b'OK' in results[0]
+ assert b'NOT ok' not in results[0]
d.addCallback(on_result)
return d
@@ -76,18 +69,12 @@ class SecurityBaseTestCase(object):
def test_secured_webserver_css_injection_vulnerability(self):
return self._run_test('-I')
- def test_secured_webserver_ticketbleed_vulnerability(self):
- return self._run_test('-T')
-
def test_secured_webserver_renegotiation_vulnerabilities(self):
return self._run_test('-R')
def test_secured_webserver_crime_vulnerability(self):
return self._run_test('-C')
- def test_secured_webserver_breach_vulnerability(self):
- return self._run_test('-B')
-
def test_secured_webserver_poodle_vulnerability(self):
return self._run_test('-O')
@@ -121,33 +108,14 @@ class SecurityBaseTestCase(object):
def test_secured_webserver_preference(self):
return self._run_test('-P')
- def test_secured_webserver_headers(self):
- return self._run_test('-h')
-
def test_secured_webserver_ciphers(self):
return self._run_test('-e')
+@pytest.mark.skipif(windows_check(), reason='windows cannot run .sh files')
+@pytest.mark.skipif(not SECURITY_TESTS, reason='skipping security tests')
@pytest.mark.security
-class DaemonSecurityTestCase(BaseTestCase, DaemonBase, SecurityBaseTestCase):
-
- if windows_check():
- skip = 'windows cannot start_core not enough arguments for format string'
-
- def __init__(self, testname):
- super(DaemonSecurityTestCase, self).__init__(testname)
- DaemonBase.__init__(self)
- SecurityBaseTestCase.__init__(self)
-
- def setUp(self):
- skip = False
- for not_http_test in ('breach', 'headers', 'ticketbleed'):
- if not_http_test in self.id().split('.')[-1]:
- self.skipTest(SecurityBaseTestCase.http_err)
- skip = True
- if not skip:
- super(DaemonSecurityTestCase, self).setUp()
-
+class TestDaemonSecurity(BaseTestCase, DaemonBase, SecurityBaseTestCase):
def set_up(self):
d = self.common_set_up()
self.port = self.listen_port
@@ -161,12 +129,10 @@ class DaemonSecurityTestCase(BaseTestCase, DaemonBase, SecurityBaseTestCase):
return d
+@pytest.mark.skipif(windows_check(), reason='windows cannot run .sh files')
+@pytest.mark.skipif(not SECURITY_TESTS, reason='skipping security tests')
@pytest.mark.security
-class WebUISecurityTestBase(WebServerTestBase, SecurityBaseTestCase):
- def __init__(self, testname):
- super(WebUISecurityTestBase, self).__init__(testname)
- SecurityBaseTestCase.__init__(self)
-
+class TestWebUISecurity(WebServerTestBase, SecurityBaseTestCase):
def start_webapi(self, arg):
self.port = self.webserver_listen_port = 8999
@@ -182,3 +148,12 @@ class WebUISecurityTestBase(WebServerTestBase, SecurityBaseTestCase):
self.deluge_web.web_api.hostlist.config['hosts'][0] = tuple(host)
self.host_id = host[0]
self.deluge_web.start()
+
+ def test_secured_webserver_headers(self):
+ return self._run_test('-h')
+
+ def test_secured_webserver_breach_vulnerability(self):
+ return self._run_test('-B')
+
+ def test_secured_webserver_ticketbleed_vulnerability(self):
+ return self._run_test('-T')
diff --git a/deluge/tests/test_sessionproxy.py b/deluge/tests/test_sessionproxy.py
index 03f3cc27e..6fbbb248b 100644
--- a/deluge/tests/test_sessionproxy.py
+++ b/deluge/tests/test_sessionproxy.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -6,19 +5,16 @@
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-
-from __future__ import unicode_literals
-
+import pytest_twisted
from twisted.internet.defer import maybeDeferred, succeed
from twisted.internet.task import Clock
import deluge.component as component
import deluge.ui.sessionproxy
+from deluge.conftest import BaseTestCase
-from .basetest import BaseTestCase
-
-class Core(object):
+class Core:
def __init__(self):
self.reset()
@@ -91,7 +87,7 @@ class Core(object):
return succeed(ret)
-class Client(object):
+class Client:
def __init__(self):
self.core = Core()
@@ -105,7 +101,7 @@ class Client(object):
client = Client()
-class SessionProxyTestCase(BaseTestCase):
+class TestSessionProxy(BaseTestCase):
def set_up(self):
self.clock = Clock()
self.patch(deluge.ui.sessionproxy, 'time', self.clock.seconds)
@@ -127,38 +123,38 @@ class SessionProxyTestCase(BaseTestCase):
return component.deregister(self.sp)
def test_startup(self):
- self.assertEqual(client.core.torrents['a'], self.sp.torrents['a'][1])
+ assert client.core.torrents['a'] == self.sp.torrents['a'][1]
- def test_get_torrent_status_no_change(self):
- d = self.sp.get_torrent_status('a', [])
- d.addCallback(self.assertEqual, client.core.torrents['a'])
- return d
+ @pytest_twisted.ensureDeferred
+ async def test_get_torrent_status_no_change(self):
+ result = await self.sp.get_torrent_status('a', [])
+ assert result == client.core.torrents['a']
- def test_get_torrent_status_change_with_cache(self):
+ @pytest_twisted.ensureDeferred
+ async def test_get_torrent_status_change_with_cache(self):
client.core.torrents['a']['key1'] = 2
- d = self.sp.get_torrent_status('a', ['key1'])
- d.addCallback(self.assertEqual, {'key1': 1})
- return d
+ result = await self.sp.get_torrent_status('a', ['key1'])
+ assert result == {'key1': 1}
- def test_get_torrent_status_change_without_cache(self):
+ @pytest_twisted.ensureDeferred
+ async def test_get_torrent_status_change_without_cache(self):
client.core.torrents['a']['key1'] = 2
self.clock.advance(self.sp.cache_time + 0.1)
- d = self.sp.get_torrent_status('a', [])
- d.addCallback(self.assertEqual, client.core.torrents['a'])
- return d
+ result = await self.sp.get_torrent_status('a', [])
+ assert result == client.core.torrents['a']
- def test_get_torrent_status_key_not_updated(self):
+ @pytest_twisted.ensureDeferred
+ async def test_get_torrent_status_key_not_updated(self):
self.clock.advance(self.sp.cache_time + 0.1)
self.sp.get_torrent_status('a', ['key1'])
client.core.torrents['a']['key2'] = 99
- d = self.sp.get_torrent_status('a', ['key2'])
- d.addCallback(self.assertEqual, {'key2': 99})
- return d
+ result = await self.sp.get_torrent_status('a', ['key2'])
+ assert result == {'key2': 99}
- def test_get_torrents_status_key_not_updated(self):
+ @pytest_twisted.ensureDeferred
+ async def test_get_torrents_status_key_not_updated(self):
self.clock.advance(self.sp.cache_time + 0.1)
self.sp.get_torrents_status({'id': ['a']}, ['key1'])
client.core.torrents['a']['key2'] = 99
- d = self.sp.get_torrents_status({'id': ['a']}, ['key2'])
- d.addCallback(self.assertEqual, {'a': {'key2': 99}})
- return d
+ result = await self.sp.get_torrents_status({'id': ['a']}, ['key2'])
+ assert result == {'a': {'key2': 99}}
diff --git a/deluge/tests/test_torrent.py b/deluge/tests/test_torrent.py
index 5da817924..36adc0fde 100644
--- a/deluge/tests/test_torrent.py
+++ b/deluge/tests/test_torrent.py
@@ -1,41 +1,37 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-
-from __future__ import print_function, unicode_literals
-
+import itertools
import os
import time
from base64 import b64encode
+from unittest import mock
-import mock
+import pytest
+import pytest_twisted
from twisted.internet import defer, reactor
from twisted.internet.task import deferLater
-from twisted.trial import unittest
import deluge.component as component
import deluge.core.torrent
import deluge.tests.common as common
from deluge._libtorrent import lt
-from deluge.common import VersionSplit, utf8_encode_structure, windows_check
+from deluge.common import VersionSplit, utf8_encode_structure
+from deluge.conftest import BaseTestCase
from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer
from deluge.core.torrent import Torrent
from deluge.core.torrentmanager import TorrentManager, TorrentState
-from .basetest import BaseTestCase
-
-class TorrentTestCase(BaseTestCase):
+class TestTorrent(BaseTestCase):
def setup_config(self):
- config_dir = common.set_tmp_config_dir()
core_config = deluge.config.Config(
'core.conf',
defaults=deluge.core.preferencesmanager.DEFAULT_PREFS,
- config_dir=config_dir,
+ config_dir=self.config_dir,
)
core_config.save()
@@ -67,7 +63,7 @@ class TorrentTestCase(BaseTestCase):
def assert_state(self, torrent, state):
torrent.update_state()
- self.assertEqual(torrent.state, state)
+ assert torrent.state == state
def get_torrent_atp(self, filename):
filename = common.get_test_data_file(filename)
@@ -85,25 +81,37 @@ class TorrentTestCase(BaseTestCase):
}
return atp
- def test_set_file_priorities(self):
+ @pytest_twisted.ensureDeferred
+ async def test_set_file_priorities(self):
+ if getattr(lt, 'file_prio_alert', None):
+ # Libtorrent 2.0.3 and later has a file_prio_alert
+ prios_set = defer.Deferred()
+ prios_set.addTimeout(1.5, reactor)
+ component.get('AlertManager').register_handler(
+ 'file_prio_alert', lambda a: prios_set.callback(True)
+ )
+ else:
+ # On older libtorrent, we just wait a while
+ prios_set = deferLater(reactor, 0.8)
+
atp = self.get_torrent_atp('dir_with_6_files.torrent')
handle = self.session.add_torrent(atp)
torrent = Torrent(handle, {})
result = torrent.get_file_priorities()
- self.assertTrue(all(x == 4 for x in result))
+ assert all(x == 4 for x in result)
new_priorities = [3, 1, 2, 0, 5, 6, 7]
torrent.set_file_priorities(new_priorities)
- self.assertEqual(torrent.get_file_priorities(), new_priorities)
+ assert torrent.get_file_priorities() == new_priorities
# Test with handle.piece_priorities as handle.file_priorities async
# updates and will return old value. Also need to remove a priority
# value as one file is much smaller than piece size so doesn't show.
- time.sleep(0.6) # Delay to wait for alert from lt
- piece_prio = handle.piece_priorities()
+ await prios_set # Delay to wait for alert from lt
+ piece_prio = handle.get_piece_priorities()
result = all(p in piece_prio for p in [3, 2, 0, 5, 6, 7])
- self.assertTrue(result)
+ assert result
def test_set_prioritize_first_last_pieces(self):
piece_indexes = [
@@ -143,19 +151,19 @@ class TorrentTestCase(BaseTestCase):
handle = self.session.add_torrent(atp)
self.torrent = Torrent(handle, {})
- priorities_original = handle.piece_priorities()
+ priorities_original = handle.get_piece_priorities()
self.torrent.set_prioritize_first_last_pieces(True)
- priorities = handle.piece_priorities()
+ priorities = handle.get_piece_priorities()
# The length of the list of new priorites is the same as the original
- self.assertEqual(len(priorities_original), len(priorities))
+ assert len(priorities_original) == len(priorities)
# Test the priority of all the pieces against the calculated indexes.
for idx, priority in enumerate(priorities):
if idx in prioritized_piece_indexes:
- self.assertEqual(priorities[idx], 7)
+ assert priorities[idx] == 7
else:
- self.assertEqual(priorities[idx], 4)
+ assert priorities[idx] == 4
# self.print_priority_list(priorities)
@@ -167,17 +175,15 @@ class TorrentTestCase(BaseTestCase):
self.torrent.set_prioritize_first_last_pieces(True)
# Reset pirorities
self.torrent.set_prioritize_first_last_pieces(False)
- priorities = handle.piece_priorities()
+ priorities = handle.get_piece_priorities()
# Test the priority of the prioritized pieces
for i in priorities:
- self.assertEqual(priorities[i], 4)
+ assert priorities[i] == 4
# self.print_priority_list(priorities)
def test_torrent_error_data_missing(self):
- if windows_check():
- raise unittest.SkipTest('unexpected end of file in bencoded string')
options = {'seed_mode': True}
filename = common.get_test_data_file('test_torrent.file.torrent')
with open(filename, 'rb') as _file:
@@ -194,8 +200,6 @@ class TorrentTestCase(BaseTestCase):
self.assert_state(torrent, 'Error')
def test_torrent_error_resume_original_state(self):
- if windows_check():
- raise unittest.SkipTest('unexpected end of file in bencoded string')
options = {'seed_mode': True, 'add_paused': True}
filename = common.get_test_data_file('test_torrent.file.torrent')
with open(filename, 'rb') as _file:
@@ -215,10 +219,8 @@ class TorrentTestCase(BaseTestCase):
torrent.force_recheck()
def test_torrent_error_resume_data_unaltered(self):
- if windows_check():
- raise unittest.SkipTest('unexpected end of file in bencoded string')
if VersionSplit(lt.__version__) >= VersionSplit('1.2.0.0'):
- raise unittest.SkipTest('Test not working as expected on lt 1.2 or greater')
+ pytest.skip('Test not working as expected on lt 1.2 or greater')
resume_data = {
'active_time': 13399,
@@ -286,7 +288,7 @@ class TorrentTestCase(BaseTestCase):
tm_resume_data = lt.bdecode(
self.core.torrentmanager.resume_data[torrent.torrent_id]
)
- self.assertEqual(tm_resume_data, resume_data)
+ assert tm_resume_data == resume_data
return deferLater(reactor, 0.5, assert_resume_data)
@@ -294,7 +296,7 @@ class TorrentTestCase(BaseTestCase):
atp = self.get_torrent_atp('test_torrent.file.torrent')
handle = self.session.add_torrent(atp)
self.torrent = Torrent(handle, {})
- self.assertEqual(self.torrent.get_eta(), 0)
+ assert self.torrent.get_eta() == 0
self.torrent.status = mock.MagicMock()
self.torrent.status.upload_payload_rate = 5000
@@ -304,18 +306,18 @@ class TorrentTestCase(BaseTestCase):
self.torrent.is_finished = True
self.torrent.options = {'stop_at_ratio': False}
# Test finished and uploading but no stop_at_ratio set.
- self.assertEqual(self.torrent.get_eta(), 0)
+ assert self.torrent.get_eta() == 0
self.torrent.options = {'stop_at_ratio': True, 'stop_ratio': 1.5}
result = self.torrent.get_eta()
- self.assertEqual(result, 2)
- self.assertIsInstance(result, int)
+ assert result == 2
+ assert isinstance(result, int)
def test_get_eta_downloading(self):
atp = self.get_torrent_atp('test_torrent.file.torrent')
handle = self.session.add_torrent(atp)
self.torrent = Torrent(handle, {})
- self.assertEqual(self.torrent.get_eta(), 0)
+ assert self.torrent.get_eta() == 0
self.torrent.status = mock.MagicMock()
self.torrent.status.download_payload_rate = 50
@@ -323,15 +325,15 @@ class TorrentTestCase(BaseTestCase):
self.torrent.status.total_wanted_done = 5000
result = self.torrent.get_eta()
- self.assertEqual(result, 100)
- self.assertIsInstance(result, int)
+ assert result == 100
+ assert isinstance(result, int)
def test_get_name_unicode(self):
"""Test retrieving a unicode torrent name from libtorrent."""
atp = self.get_torrent_atp('unicode_file.torrent')
handle = self.session.add_torrent(atp)
self.torrent = Torrent(handle, {})
- self.assertEqual(self.torrent.get_name(), 'সুকুমার রায়.txt')
+ assert self.torrent.get_name() == 'সুকুমার রায়.txt'
def test_rename_unicode(self):
"""Test renaming file/folders with unicode filenames."""
@@ -342,15 +344,32 @@ class TorrentTestCase(BaseTestCase):
TorrentManager.save_resume_data = mock.MagicMock
result = self.torrent.rename_folder('unicode_filenames', 'Горбачёв')
- self.assertIsInstance(result, defer.DeferredList)
+ assert isinstance(result, defer.DeferredList)
result = self.torrent.rename_files([[0, 'new_рбачёв']])
- self.assertIsNone(result)
+ assert result is None
def test_connect_peer_port(self):
"""Test to ensure port is int for libtorrent"""
atp = self.get_torrent_atp('test_torrent.file.torrent')
handle = self.session.add_torrent(atp)
self.torrent = Torrent(handle, {})
- self.assertFalse(self.torrent.connect_peer('127.0.0.1', 'text'))
- self.assertTrue(self.torrent.connect_peer('127.0.0.1', '1234'))
+ assert not self.torrent.connect_peer('127.0.0.1', 'text')
+ assert self.torrent.connect_peer('127.0.0.1', '1234')
+
+ def test_status_cache(self):
+ atp = self.get_torrent_atp('test_torrent.file.torrent')
+ handle = self.session.add_torrent(atp)
+ mock_time = mock.Mock(return_value=time.time())
+ with mock.patch('time.time', mock_time):
+ torrent = Torrent(handle, {})
+ counter = itertools.count()
+ handle.status = mock.Mock(side_effect=counter.__next__)
+ first_status = torrent.get_lt_status()
+ assert first_status == 0, 'sanity check'
+ assert first_status == torrent.status, 'cached status should be used'
+ assert torrent.get_lt_status() == 1, 'status should update'
+ assert torrent.status == 1
+ # Advance time and verify cache expires and updates
+ mock_time.return_value += 10
+ assert torrent.status == 2
diff --git a/deluge/tests/test_torrentmanager.py b/deluge/tests/test_torrentmanager.py
index e0ff09efc..0ead27230 100644
--- a/deluge/tests/test_torrentmanager.py
+++ b/deluge/tests/test_torrentmanager.py
@@ -1,38 +1,34 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os
import shutil
import warnings
from base64 import b64encode
+from unittest import mock
-import mock
import pytest
-from twisted.internet import defer, task
-from twisted.trial import unittest
+import pytest_twisted
+from twisted.internet import reactor, task
from deluge import component
-from deluge.common import windows_check
+from deluge.bencode import bencode
+from deluge.conftest import BaseTestCase
from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer
from deluge.error import InvalidTorrentError
from . import common
-from .basetest import BaseTestCase
warnings.filterwarnings('ignore', category=RuntimeWarning)
warnings.resetwarnings()
-class TorrentmanagerTestCase(BaseTestCase):
+class TestTorrentmanager(BaseTestCase):
def set_up(self):
- self.config_dir = common.set_tmp_config_dir()
self.rpcserver = RPCServer(listen=False)
self.core = Core()
self.core.config.config['lsd'] = False
@@ -48,7 +44,7 @@ class TorrentmanagerTestCase(BaseTestCase):
return component.shutdown().addCallback(on_shutdown)
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_remove_torrent(self):
filename = common.get_test_data_file('test.torrent')
with open(filename, 'rb') as _file:
@@ -56,9 +52,9 @@ class TorrentmanagerTestCase(BaseTestCase):
torrent_id = yield self.core.add_torrent_file_async(
filename, b64encode(filedump), {}
)
- self.assertTrue(self.tm.remove(torrent_id, False))
+ assert self.tm.remove(torrent_id, False)
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_remove_magnet(self):
"""Test remove magnet before received metadata and delete_copies is True"""
magnet = 'magnet:?xt=urn:btih:ab570cdd5a17ea1b61e970bb72047de141bce173'
@@ -66,9 +62,10 @@ class TorrentmanagerTestCase(BaseTestCase):
self.core.config.config['copy_torrent_file'] = True
self.core.config.config['del_copy_torrent_file'] = True
torrent_id = yield self.core.add_torrent_magnet(magnet, options)
- self.assertTrue(self.tm.remove(torrent_id, False))
+ assert self.tm.remove(torrent_id, False)
- def test_prefetch_metadata(self):
+ @pytest_twisted.ensureDeferred
+ async def test_prefetch_metadata(self):
from deluge._libtorrent import lt
with open(common.get_test_data_file('test.torrent'), 'rb') as _file:
@@ -81,47 +78,55 @@ class TorrentmanagerTestCase(BaseTestCase):
magnet = 'magnet:?xt=urn:btih:ab570cdd5a17ea1b61e970bb72047de141bce173'
d = self.tm.prefetch_metadata(magnet, 30)
- self.tm.on_alert_metadata_received(mock_alert)
+ # Make sure to use calllater, because the above prefetch call won't
+ # actually start running until we await it.
+ reactor.callLater(0, self.tm.on_alert_metadata_received, mock_alert)
expected = (
'ab570cdd5a17ea1b61e970bb72047de141bce173',
- {
- b'piece length': 32768,
- b'sha1': (
- b'2\xce\xb6\xa8"\xd7\xf0\xd4\xbf\xdc^K\xba\x1bh'
- b'\x9d\xc5\xb7\xac\xdd'
- ),
- b'name': b'azcvsupdater_2.6.2.jar',
- b'private': 0,
- b'pieces': (
- b"\xdb\x04B\x05\xc3'\xdab\xb8su97\xa9u"
- b'\xca<w\\\x1ef\xd4\x9b\x16\xa9}\xc0\x9f:\xfd'
- b'\x97qv\x83\xa2"\xef\x9d7\x0by!\rl\xe5v\xb7'
- b'\x18{\xf7/"P\xe9\x8d\x01D\x9e8\xbd\x16\xe3'
- b'\xfb-\x9d\xaa\xbcM\x11\xba\x92\xfc\x13F\xf0'
- b'\x1c\x86x+\xc8\xd0S\xa9\x90`\xa1\xe4\x82\xe8'
- b'\xfc\x08\xf7\xe3\xe5\xf6\x85\x1c%\xe7%\n\xed'
- b'\xc0\x1f\xa1;\x9a\xea\xcf\x90\x0c/F>\xdf\xdagA'
- b'\xc42|\xda\x82\xf5\xa6b\xa1\xb8#\x80wI\xd8f'
- b'\xf8\xbd\xacW\xab\xc3s\xe0\xbbw\xf2K\xbe\xee'
- b'\xa8rG\xe1W\xe8\xb7\xc2i\xf3\xd8\xaf\x9d\xdc'
- b'\xd0#\xf4\xc1\x12u\xcd\x0bE?:\xe8\x9c\x1cu'
- b'\xabb(oj\r^\xd5\xd5A\x83\x88\x9a\xa1J\x1c?'
- b'\xa1\xd6\x8c\x83\x9e&'
- ),
- b'length': 307949,
- b'name.utf-8': b'azcvsupdater_2.6.2.jar',
- b'ed2k': b'>p\xefl\xfa]\x95K\x1b^\xc2\\;;e\xb7',
- },
+ b64encode(
+ bencode(
+ {
+ b'piece length': 32768,
+ b'sha1': (
+ b'2\xce\xb6\xa8"\xd7\xf0\xd4\xbf\xdc^K\xba\x1bh'
+ b'\x9d\xc5\xb7\xac\xdd'
+ ),
+ b'name': b'azcvsupdater_2.6.2.jar',
+ b'private': 0,
+ b'pieces': (
+ b"\xdb\x04B\x05\xc3'\xdab\xb8su97\xa9u"
+ b'\xca<w\\\x1ef\xd4\x9b\x16\xa9}\xc0\x9f:\xfd'
+ b'\x97qv\x83\xa2"\xef\x9d7\x0by!\rl\xe5v\xb7'
+ b'\x18{\xf7/"P\xe9\x8d\x01D\x9e8\xbd\x16\xe3'
+ b'\xfb-\x9d\xaa\xbcM\x11\xba\x92\xfc\x13F\xf0'
+ b'\x1c\x86x+\xc8\xd0S\xa9\x90`\xa1\xe4\x82\xe8'
+ b'\xfc\x08\xf7\xe3\xe5\xf6\x85\x1c%\xe7%\n\xed'
+ b'\xc0\x1f\xa1;\x9a\xea\xcf\x90\x0c/F>\xdf\xdagA'
+ b'\xc42|\xda\x82\xf5\xa6b\xa1\xb8#\x80wI\xd8f'
+ b'\xf8\xbd\xacW\xab\xc3s\xe0\xbbw\xf2K\xbe\xee'
+ b'\xa8rG\xe1W\xe8\xb7\xc2i\xf3\xd8\xaf\x9d\xdc'
+ b'\xd0#\xf4\xc1\x12u\xcd\x0bE?:\xe8\x9c\x1cu'
+ b'\xabb(oj\r^\xd5\xd5A\x83\x88\x9a\xa1J\x1c?'
+ b'\xa1\xd6\x8c\x83\x9e&'
+ ),
+ b'length': 307949,
+ b'name.utf-8': b'azcvsupdater_2.6.2.jar',
+ b'ed2k': b'>p\xefl\xfa]\x95K\x1b^\xc2\\;;e\xb7',
+ }
+ )
+ ),
)
- self.assertEqual(expected, self.successResultOf(d))
+ assert expected == await d
- def test_prefetch_metadata_timeout(self):
+ @pytest_twisted.ensureDeferred
+ async def test_prefetch_metadata_timeout(self):
magnet = 'magnet:?xt=urn:btih:ab570cdd5a17ea1b61e970bb72047de141bce173'
d = self.tm.prefetch_metadata(magnet, 30)
self.clock.advance(30)
- expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', None)
- return d.addCallback(self.assertEqual, expected)
+ result = await d
+ expected = ('ab570cdd5a17ea1b61e970bb72047de141bce173', b'')
+ assert result == expected
@pytest.mark.todo
def test_remove_torrent_false(self):
@@ -129,20 +134,15 @@ class TorrentmanagerTestCase(BaseTestCase):
common.todo_test(self)
def test_remove_invalid_torrent(self):
- self.assertRaises(
- InvalidTorrentError, self.tm.remove, 'torrentidthatdoesntexist'
- )
+ with pytest.raises(InvalidTorrentError):
+ self.tm.remove('torrentidthatdoesntexist')
- def test_open_state_from_python2(self):
- """Open a Python2 state with a UTF-8 encoded torrent filename."""
+ def test_open_state(self):
+ """Open a state with a UTF-8 encoded torrent filename."""
shutil.copy(
common.get_test_data_file('utf8_filename_torrents.state'),
os.path.join(self.config_dir, 'state', 'torrents.state'),
)
- if windows_check():
- raise unittest.SkipTest(
- 'Windows ModuleNotFoundError due to Linux line ending'
- )
state = self.tm.open_state()
- self.assertEqual(len(state.torrents), 1)
+ assert len(state.torrents) == 1
diff --git a/deluge/tests/test_torrentview.py b/deluge/tests/test_torrentview.py
index 590760d1e..8d0568866 100644
--- a/deluge/tests/test_torrentview.py
+++ b/deluge/tests/test_torrentview.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Bro <bro.development@gmail.com>
# Copyright (C) 2014 Calum Lind <calumlind@gmail.com>
@@ -8,18 +7,13 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import pytest
-from twisted.trial import unittest
import deluge.component as component
from deluge.configmanager import ConfigManager
+from deluge.conftest import BaseTestCase
from deluge.i18n import setup_translation
-from . import common
-from .basetest import BaseTestCase
-
# Allow running other tests without GTKUI dependencies available
try:
# pylint: disable=ungrouped-imports
@@ -40,7 +34,7 @@ setup_translation()
@pytest.mark.gtkui
-class TorrentviewTestCase(BaseTestCase):
+class TestTorrentview(BaseTestCase):
default_column_index = [
'filter',
@@ -66,6 +60,7 @@ class TorrentviewTestCase(BaseTestCase):
'Added',
'Completed',
'Complete Seen',
+ 'Last Transfer',
'Tracker',
'Download Folder',
'Owner',
@@ -99,6 +94,7 @@ class TorrentviewTestCase(BaseTestCase):
int,
int,
int,
+ int,
str,
str, # Tracker
str,
@@ -108,9 +104,8 @@ class TorrentviewTestCase(BaseTestCase):
def set_up(self):
if libs_available is False:
- raise unittest.SkipTest('GTKUI dependencies not available')
+ pytest.skip('GTKUI dependencies not available')
- common.set_tmp_config_dir()
# MainWindow loads this config file, so lets make sure it contains the defaults
ConfigManager('gtk3ui.conf', defaults=DEFAULT_PREFS)
self.mainwindow = MainWindow()
@@ -122,36 +117,23 @@ class TorrentviewTestCase(BaseTestCase):
return component.shutdown()
def test_torrentview_columns(self):
-
- self.assertEqual(
- self.torrentview.column_index, TorrentviewTestCase.default_column_index
- )
- self.assertEqual(
- self.torrentview.liststore_columns,
- TorrentviewTestCase.default_liststore_columns,
- )
- self.assertEqual(
- self.torrentview.columns['Download Folder'].column_indices, [29]
- )
+ assert self.torrentview.column_index == self.default_column_index
+ assert self.torrentview.liststore_columns == self.default_liststore_columns
+ assert self.torrentview.columns['Download Folder'].column_indices == [30]
def test_add_column(self):
-
# Add a text column
test_col = 'Test column'
self.torrentview.add_text_column(test_col, status_field=['label'])
- self.assertEqual(
- len(self.torrentview.liststore_columns),
- len(TorrentviewTestCase.default_liststore_columns) + 1,
- )
- self.assertEqual(
- len(self.torrentview.column_index),
- len(TorrentviewTestCase.default_column_index) + 1,
+ assert (
+ len(self.torrentview.liststore_columns)
+ == len(self.default_liststore_columns) + 1
)
- self.assertEqual(self.torrentview.column_index[-1], test_col)
- self.assertEqual(self.torrentview.columns[test_col].column_indices, [32])
+ assert len(self.torrentview.column_index) == len(self.default_column_index) + 1
+ assert self.torrentview.column_index[-1] == test_col
+ assert self.torrentview.columns[test_col].column_indices == [33]
def test_add_columns(self):
-
# Add a text column
test_col = 'Test column'
self.torrentview.add_text_column(test_col, status_field=['label'])
@@ -160,50 +142,35 @@ class TorrentviewTestCase(BaseTestCase):
test_col2 = 'Test column2'
self.torrentview.add_text_column(test_col2, status_field=['label2'])
- self.assertEqual(
- len(self.torrentview.liststore_columns),
- len(TorrentviewTestCase.default_liststore_columns) + 2,
- )
- self.assertEqual(
- len(self.torrentview.column_index),
- len(TorrentviewTestCase.default_column_index) + 2,
+ assert (
+ len(self.torrentview.liststore_columns)
+ == len(self.default_liststore_columns) + 2
)
+ assert len(self.torrentview.column_index) == len(self.default_column_index) + 2
# test_col
- self.assertEqual(self.torrentview.column_index[-2], test_col)
- self.assertEqual(self.torrentview.columns[test_col].column_indices, [32])
+ assert self.torrentview.column_index[-2] == test_col
+ assert self.torrentview.columns[test_col].column_indices == [33]
# test_col2
- self.assertEqual(self.torrentview.column_index[-1], test_col2)
- self.assertEqual(self.torrentview.columns[test_col2].column_indices, [33])
+ assert self.torrentview.column_index[-1] == test_col2
+ assert self.torrentview.columns[test_col2].column_indices == [34]
def test_remove_column(self):
-
# Add and remove text column
test_col = 'Test column'
self.torrentview.add_text_column(test_col, status_field=['label'])
self.torrentview.remove_column(test_col)
- self.assertEqual(
- len(self.torrentview.liststore_columns),
- len(TorrentviewTestCase.default_liststore_columns),
- )
- self.assertEqual(
- len(self.torrentview.column_index),
- len(TorrentviewTestCase.default_column_index),
- )
- self.assertEqual(
- self.torrentview.column_index[-1],
- TorrentviewTestCase.default_column_index[-1],
- )
- self.assertEqual(
- self.torrentview.columns[
- TorrentviewTestCase.default_column_index[-1]
- ].column_indices,
- [31],
+ assert len(self.torrentview.liststore_columns) == len(
+ self.default_liststore_columns
)
+ assert len(self.torrentview.column_index) == len(self.default_column_index)
+ assert self.torrentview.column_index[-1] == self.default_column_index[-1]
+ assert self.torrentview.columns[
+ self.default_column_index[-1]
+ ].column_indices == [32]
def test_remove_columns(self):
-
# Add two columns
test_col = 'Test column'
self.torrentview.add_text_column(test_col, status_field=['label'])
@@ -212,74 +179,47 @@ class TorrentviewTestCase(BaseTestCase):
# Remove test_col
self.torrentview.remove_column(test_col)
- self.assertEqual(
- len(self.torrentview.liststore_columns),
- len(TorrentviewTestCase.default_liststore_columns) + 1,
+ assert (
+ len(self.torrentview.liststore_columns)
+ == len(self.default_liststore_columns) + 1
)
- self.assertEqual(
- len(self.torrentview.column_index),
- len(TorrentviewTestCase.default_column_index) + 1,
- )
- self.assertEqual(self.torrentview.column_index[-1], test_col2)
- self.assertEqual(self.torrentview.columns[test_col2].column_indices, [32])
+ assert len(self.torrentview.column_index) == len(self.default_column_index) + 1
+ assert self.torrentview.column_index[-1] == test_col2
+ assert self.torrentview.columns[test_col2].column_indices == [33]
# Remove test_col2
self.torrentview.remove_column(test_col2)
- self.assertEqual(
- len(self.torrentview.liststore_columns),
- len(TorrentviewTestCase.default_liststore_columns),
- )
- self.assertEqual(
- len(self.torrentview.column_index),
- len(TorrentviewTestCase.default_column_index),
- )
- self.assertEqual(
- self.torrentview.column_index[-1],
- TorrentviewTestCase.default_column_index[-1],
- )
- self.assertEqual(
- self.torrentview.columns[
- TorrentviewTestCase.default_column_index[-1]
- ].column_indices,
- [31],
+ assert len(self.torrentview.liststore_columns) == len(
+ self.default_liststore_columns
)
+ assert len(self.torrentview.column_index) == len(self.default_column_index)
+ assert self.torrentview.column_index[-1] == self.default_column_index[-1]
+ assert self.torrentview.columns[
+ self.default_column_index[-1]
+ ].column_indices == [32]
def test_add_remove_column_multiple_types(self):
-
# Add a column with multiple column types
test_col3 = 'Test column3'
self.torrentview.add_progress_column(
test_col3, status_field=['progress', 'label3'], col_types=[float, str]
)
- self.assertEqual(
- len(self.torrentview.liststore_columns),
- len(TorrentviewTestCase.default_liststore_columns) + 2,
- )
- self.assertEqual(
- len(self.torrentview.column_index),
- len(TorrentviewTestCase.default_column_index) + 1,
+ assert (
+ len(self.torrentview.liststore_columns)
+ == len(self.default_liststore_columns) + 2
)
- self.assertEqual(self.torrentview.column_index[-1], test_col3)
- self.assertEqual(self.torrentview.columns[test_col3].column_indices, [32, 33])
+ assert len(self.torrentview.column_index) == len(self.default_column_index) + 1
+ assert self.torrentview.column_index[-1] == test_col3
+ assert self.torrentview.columns[test_col3].column_indices == [33, 34]
# Remove multiple column-types column
self.torrentview.remove_column(test_col3)
- self.assertEqual(
- len(self.torrentview.liststore_columns),
- len(TorrentviewTestCase.default_liststore_columns),
- )
- self.assertEqual(
- len(self.torrentview.column_index),
- len(TorrentviewTestCase.default_column_index),
- )
- self.assertEqual(
- self.torrentview.column_index[-1],
- TorrentviewTestCase.default_column_index[-1],
- )
- self.assertEqual(
- self.torrentview.columns[
- TorrentviewTestCase.default_column_index[-1]
- ].column_indices,
- [31],
+ assert len(self.torrentview.liststore_columns) == len(
+ self.default_liststore_columns
)
+ assert len(self.torrentview.column_index) == len(self.default_column_index)
+ assert self.torrentview.column_index[-1] == self.default_column_index[-1]
+ assert self.torrentview.columns[
+ self.default_column_index[-1]
+ ].column_indices == [32]
diff --git a/deluge/tests/test_tracker_icons.py b/deluge/tests/test_tracker_icons.py
index e18d33987..2f793d12e 100644
--- a/deluge/tests/test_tracker_icons.py
+++ b/deluge/tests/test_tracker_icons.py
@@ -1,34 +1,25 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-
-from __future__ import unicode_literals
+import os.path
import pytest
-from twisted.trial.unittest import SkipTest
+import pytest_twisted
import deluge.component as component
import deluge.ui.tracker_icons
-from deluge.common import windows_check
+from deluge.conftest import BaseTestCase
from deluge.ui.tracker_icons import TrackerIcon, TrackerIcons
from . import common
-from .basetest import BaseTestCase
-common.set_tmp_config_dir()
-deluge.ui.tracker_icons.PIL_INSTALLED = False
common.disable_new_release_check()
@pytest.mark.internet
-class TrackerIconsTestCase(BaseTestCase):
-
- if windows_check():
- skip = 'cannot use os.path.samefile to compair on windows(unix only)'
-
+class TestTrackerIcons(BaseTestCase):
def set_up(self):
# Disable resizing with Pillow for consistency.
self.patch(deluge.ui.tracker_icons, 'Image', None)
@@ -37,41 +28,52 @@ class TrackerIconsTestCase(BaseTestCase):
def tear_down(self):
return component.shutdown()
- def test_get_deluge_png(self):
+ @pytest_twisted.ensureDeferred
+ async def test_get_deluge_png(self, mock_mkstemp):
# Deluge has a png favicon link
icon = TrackerIcon(common.get_test_data_file('deluge.png'))
- d = self.icons.fetch('deluge-torrent.org')
- d.addCallback(self.assertNotIdentical, None)
- d.addCallback(self.assertEqual, icon)
- return d
+ result = await self.icons.fetch('deluge-torrent.org')
+ assert result == icon
+ assert not os.path.isfile(mock_mkstemp[1])
- def test_get_google_ico(self):
+ @pytest_twisted.ensureDeferred
+ async def test_get_google_ico(self):
# Google doesn't have any icon links
# So instead we'll grab its favicon.ico
icon = TrackerIcon(common.get_test_data_file('google.ico'))
- d = self.icons.fetch('www.google.com')
- d.addCallback(self.assertNotIdentical, None)
- d.addCallback(self.assertEqual, icon)
- return d
+ result = await self.icons.fetch('www.google.com')
+ assert result == icon
- def test_get_google_ico_with_redirect(self):
+ @pytest_twisted.ensureDeferred
+ async def test_get_google_ico_hebrew(self):
+ """Test that Google.co.il page is read as UTF-8"""
+ icon = TrackerIcon(common.get_test_data_file('google.ico'))
+ result = await self.icons.fetch('www.google.co.il')
+ assert result == icon
+
+ @pytest_twisted.ensureDeferred
+ async def test_get_google_ico_with_redirect(self):
# google.com redirects to www.google.com
icon = TrackerIcon(common.get_test_data_file('google.ico'))
- d = self.icons.fetch('google.com')
- d.addCallback(self.assertNotIdentical, None)
- d.addCallback(self.assertEqual, icon)
- return d
+ result = await self.icons.fetch('google.com')
+ assert result == icon
- def test_get_seo_ico_with_sni(self):
+ @pytest.mark.skip(reason='Site removed favicon, new SNI test will be needed')
+ @pytest_twisted.ensureDeferred
+ async def test_get_seo_svg_with_sni(self):
# seo using certificates with SNI support only
- raise SkipTest('Site certificate expired')
- icon = TrackerIcon(common.get_test_data_file('seo.ico'))
- d = self.icons.fetch('www.seo.com')
- d.addCallback(self.assertNotIdentical, None)
- d.addCallback(self.assertEqual, icon)
- return d
+ icon = TrackerIcon(common.get_test_data_file('seo.svg'))
+ result = await self.icons.fetch('www.seo.com')
+ assert result == icon
+
+ @pytest_twisted.ensureDeferred
+ async def test_get_empty_string_tracker(self):
+ result = await self.icons.fetch('')
+ assert result is None
- def test_get_empty_string_tracker(self):
- d = self.icons.fetch('')
- d.addCallback(self.assertIdentical, None)
- return d
+ @pytest_twisted.ensureDeferred
+ async def test_invalid_host(self, mock_mkstemp):
+ """Test that TrackerIcon can handle invalid hostname"""
+ result = await self.icons.fetch('deluge.example.com')
+ assert not result
+ assert not os.path.isfile(mock_mkstemp[1])
diff --git a/deluge/tests/test_transfer.py b/deluge/tests/test_transfer.py
index a04830325..92e349b5d 100644
--- a/deluge/tests/test_transfer.py
+++ b/deluge/tests/test_transfer.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 Bro <bro.development@gmail.com>
#
@@ -7,12 +6,10 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import base64
+import pytest
import rencode
-from twisted.trial import unittest
import deluge.log
from deluge.transfer import DelugeTransferProtocol
@@ -112,8 +109,9 @@ class TransferTestClass(DelugeTransferProtocol):
self.message_received(request)
-class DelugeTransferProtocolTestCase(unittest.TestCase):
- def setUp(self): # NOQA: N803
+class TestDelugeTransferProtocol:
+ @pytest.fixture(autouse=True)
+ def set_up(self):
"""
The expected messages corresponds to the test messages (msg1, msg2) after they've been processed
by DelugeTransferProtocol.send, which means that they've first been encoded with rencode,
@@ -160,7 +158,7 @@ class DelugeTransferProtocolTestCase(unittest.TestCase):
# Get the data as sent by DelugeTransferProtocol
messages = self.transfer.get_messages_out_joined()
base64_encoded = base64.b64encode(messages)
- self.assertEqual(base64_encoded, self.msg1_expected_compressed_base64)
+ assert base64_encoded == self.msg1_expected_compressed_base64
def test_receive_one_message(self):
"""
@@ -173,7 +171,7 @@ class DelugeTransferProtocolTestCase(unittest.TestCase):
)
# Get the data as sent by DelugeTransferProtocol
messages = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg1), rencode.dumps(messages))
+ assert rencode.dumps(self.msg1) == rencode.dumps(messages)
def test_receive_old_message(self):
"""
@@ -181,9 +179,9 @@ class DelugeTransferProtocolTestCase(unittest.TestCase):
"""
self.transfer.dataReceived(rencode.dumps(self.msg1))
- self.assertEqual(len(self.transfer.get_messages_in()), 0)
- self.assertEqual(self.transfer._message_length, 0)
- self.assertEqual(len(self.transfer._buffer), 0)
+ assert len(self.transfer.get_messages_in()) == 0
+ assert self.transfer._message_length == 0
+ assert len(self.transfer._buffer) == 0
def test_receive_two_concatenated_messages(self):
"""
@@ -198,9 +196,9 @@ class DelugeTransferProtocolTestCase(unittest.TestCase):
# Get the data as sent by DelugeTransferProtocol
message1 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg1), rencode.dumps(message1))
+ assert rencode.dumps(self.msg1) == rencode.dumps(message1)
message2 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg2), rencode.dumps(message2))
+ assert rencode.dumps(self.msg2) == rencode.dumps(message2)
def test_receive_three_messages_in_parts(self):
"""
@@ -237,20 +235,17 @@ class DelugeTransferProtocolTestCase(unittest.TestCase):
else:
expected_msgs_received_count = 0
# Verify that the expected number of complete messages has arrived
- self.assertEqual(
- expected_msgs_received_count, len(self.transfer.get_messages_in())
- )
+ assert expected_msgs_received_count == len(self.transfer.get_messages_in())
# Get the data as received by DelugeTransferProtocol
message1 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg1), rencode.dumps(message1))
+ assert rencode.dumps(self.msg1) == rencode.dumps(message1)
message2 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg2), rencode.dumps(message2))
+ assert rencode.dumps(self.msg2) == rencode.dumps(message2)
message3 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg1), rencode.dumps(message3))
+ assert rencode.dumps(self.msg1) == rencode.dumps(message3)
# Remove underscore to enable test, or run the test directly:
- # tests $ trial test_transfer.DelugeTransferProtocolTestCase._test_rencode_fail_protocol
def _test_rencode_fail_protocol(self):
"""
This test tries to test the protocol that relies on errors from rencode.
@@ -317,11 +312,11 @@ class DelugeTransferProtocolTestCase(unittest.TestCase):
# Get the data as received by DelugeTransferProtocol
message1 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg1), rencode.dumps(message1))
+ assert rencode.dumps(self.msg1) == rencode.dumps(message1)
message2 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg2), rencode.dumps(message2))
+ assert rencode.dumps(self.msg2) == rencode.dumps(message2)
message3 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg1), rencode.dumps(message3))
+ assert rencode.dumps(self.msg1) == rencode.dumps(message3)
def test_receive_middle_of_header(self):
"""
@@ -344,19 +339,19 @@ class DelugeTransferProtocolTestCase(unittest.TestCase):
self.transfer.dataReceived(two_concatenated[: first_len + 2])
# Should be 1 message in the list
- self.assertEqual(1, len(self.transfer.get_messages_in()))
+ assert 1 == len(self.transfer.get_messages_in())
# Send the rest
self.transfer.dataReceived(two_concatenated[first_len + 2 :])
# Should be 2 messages in the list
- self.assertEqual(2, len(self.transfer.get_messages_in()))
+ assert 2 == len(self.transfer.get_messages_in())
# Get the data as sent by DelugeTransferProtocol
message1 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg1), rencode.dumps(message1))
+ assert rencode.dumps(self.msg1) == rencode.dumps(message1)
message2 = self.transfer.get_messages_in().pop(0)
- self.assertEqual(rencode.dumps(self.msg2), rencode.dumps(message2))
+ assert rencode.dumps(self.msg2) == rencode.dumps(message2)
# Needs file containing big data structure e.g. like thetorrent list as it is transfered by the daemon
# def test_simulate_big_transfer(self):
diff --git a/deluge/tests/test_ui_common.py b/deluge/tests/test_ui_common.py
index b0c311183..ee97259de 100644
--- a/deluge/tests/test_ui_common.py
+++ b/deluge/tests/test_ui_common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -6,30 +5,19 @@
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-from six import assertCountEqual
-from twisted.trial import unittest
-
-from deluge.common import windows_check
from deluge.ui.common import TorrentInfo
from . import common
-class UICommonTestCase(unittest.TestCase):
- def setUp(self): # NOQA: N803
- pass
-
- def tearDown(self): # NOQA: N803
- pass
-
+class TestUICommon:
def test_hash_optional_single_file(self):
"""Ensure single file with `ed2k` and `sha1` keys are not in filetree output."""
filename = common.get_test_data_file('test.torrent')
files_tree = {'azcvsupdater_2.6.2.jar': (0, 307949, True)}
ti = TorrentInfo(filename, filetree=1)
- self.assertEqual(ti.files_tree, files_tree)
+ assert ti.files_tree == files_tree
files_tree2 = {
'contents': {
@@ -42,7 +30,7 @@ class UICommonTestCase(unittest.TestCase):
}
}
ti = TorrentInfo(filename, filetree=2)
- self.assertEqual(ti.files_tree, files_tree2)
+ assert ti.files_tree == files_tree2
def test_hash_optional_multi_file(self):
"""Ensure multi-file with `filehash` and `ed2k` are keys not in filetree output."""
@@ -54,7 +42,7 @@ class UICommonTestCase(unittest.TestCase):
}
}
ti = TorrentInfo(filename, filetree=1)
- self.assertEqual(ti.files_tree, files_tree)
+ assert ti.files_tree == files_tree
filestree2 = {
'contents': {
@@ -83,14 +71,14 @@ class UICommonTestCase(unittest.TestCase):
'type': 'dir',
}
ti = TorrentInfo(filename, filetree=2)
- self.assertEqual(ti.files_tree, filestree2)
+ assert ti.files_tree == filestree2
def test_hash_optional_md5sum(self):
# Ensure `md5sum` key is not included in filetree output
filename = common.get_test_data_file('md5sum.torrent')
files_tree = {'test': {'lol': (0, 4, True), 'rofl': (1, 5, True)}}
ti = TorrentInfo(filename, filetree=1)
- self.assertEqual(ti.files_tree, files_tree)
+ assert ti.files_tree == files_tree
ti = TorrentInfo(filename, filetree=2)
files_tree2 = {
'contents': {
@@ -118,16 +106,14 @@ class UICommonTestCase(unittest.TestCase):
},
'type': 'dir',
}
- self.assertEqual(ti.files_tree, files_tree2)
+ assert ti.files_tree == files_tree2
def test_utf8_encoded_paths(self):
filename = common.get_test_data_file('test.torrent')
ti = TorrentInfo(filename)
- self.assertTrue('azcvsupdater_2.6.2.jar' in ti.files_tree)
+ assert 'azcvsupdater_2.6.2.jar' in ti.files_tree
def test_utf8_encoded_paths2(self):
- if windows_check():
- raise unittest.SkipTest('on windows KeyError: unicode_filenames')
filename = common.get_test_data_file('unicode_filenames.torrent')
filepath1 = '\u30c6\u30af\u30b9\u30fb\u30c6\u30af\u30b5\u30f3.mkv'
filepath2 = (
@@ -140,11 +126,11 @@ class UICommonTestCase(unittest.TestCase):
ti = TorrentInfo(filename)
files_tree = ti.files_tree['unicode_filenames']
- self.assertIn(filepath1, files_tree)
- self.assertIn(filepath2, files_tree)
- self.assertIn(filepath3, files_tree)
- self.assertIn(filepath4, files_tree)
- self.assertIn(filepath5, files_tree)
+ assert filepath1 in files_tree
+ assert filepath2 in files_tree
+ assert filepath3 in files_tree
+ assert filepath4 in files_tree
+ assert filepath5 in files_tree
result_files = [
{
@@ -170,4 +156,4 @@ class UICommonTestCase(unittest.TestCase):
{'download': True, 'path': 'unicode_filenames/' + filepath1, 'size': 1771},
]
- assertCountEqual(self, ti.files, result_files)
+ assert len(ti.files) == len(result_files)
diff --git a/deluge/tests/test_ui_console.py b/deluge/tests/test_ui_console.py
index 3667c608e..34398ee19 100644
--- a/deluge/tests/test_ui_console.py
+++ b/deluge/tests/test_ui_console.py
@@ -1,35 +1,30 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import argparse
+import pytest
+
from deluge.ui.console.cmdline.commands.add import Command
from deluge.ui.console.cmdline.commands.config import json_eval
from deluge.ui.console.widgets.fields import TextInput
-from .basetest import BaseTestCase
-
-class MockParent(object):
+class MockParent:
def __init__(self):
self.border_off_x = 1
self.pane_width = 20
self.encoding = 'utf8'
-class UIConsoleFieldTestCase(BaseTestCase):
- def setUp(self): # NOQA: N803
+class TestUIConsoleField:
+ @pytest.fixture(autouse=True)
+ def set_up(self):
self.parent = MockParent()
- def tearDown(self): # NOQA: N803
- pass
-
def test_text_input(self):
def move_func(self, r, c):
self._cursor_row = r
@@ -44,48 +39,42 @@ class UIConsoleFieldTestCase(BaseTestCase):
'/text/field/file/path',
complete=False,
)
- self.assertTrue(t)
- self.assertTrue(t.handle_read(33))
-
-
-class UIConsoleCommandsTestCase(BaseTestCase):
- def setUp(self):
- pass
+ assert t
+ assert t.handle_read(33)
- def tearDown(self):
- pass
+class TestUIConsoleCommands:
def test_add_move_completed(self):
completed_path = 'completed_path'
parser = argparse.ArgumentParser()
cmd = Command()
cmd.add_arguments(parser)
args = parser.parse_args(['torrent', '-m', completed_path])
- self.assertEqual(args.move_completed_path, completed_path)
+ assert args.move_completed_path == completed_path
args = parser.parse_args(['torrent', '--move-path', completed_path])
- self.assertEqual(args.move_completed_path, completed_path)
+ assert args.move_completed_path == completed_path
def test_config_json_eval(self):
- self.assertEqual(json_eval('/downloads'), '/downloads')
- self.assertEqual(json_eval('/dir/with space'), '/dir/with space')
- self.assertEqual(json_eval('c:\\\\downloads'), 'c:\\\\downloads')
- self.assertEqual(json_eval('c:/downloads'), 'c:/downloads')
+ assert json_eval('/downloads') == '/downloads'
+ assert json_eval('/dir/with space') == '/dir/with space'
+ assert json_eval('c:\\\\downloads') == 'c:\\\\downloads'
+ assert json_eval('c:/downloads') == 'c:/downloads'
# Ensure newlines are split and only first setting is used.
- self.assertEqual(json_eval('setting\nwithneline'), 'setting')
+ assert json_eval('setting\nwithneline') == 'setting'
# Allow both parentheses and square brackets.
- self.assertEqual(json_eval('(8000, 8001)'), [8000, 8001])
- self.assertEqual(json_eval('[8000, 8001]'), [8000, 8001])
- self.assertEqual(json_eval('["abc", "def"]'), ['abc', 'def'])
- self.assertEqual(json_eval('{"foo": "bar"}'), {'foo': 'bar'})
- self.assertEqual(json_eval('{"number": 1234}'), {'number': 1234})
+ assert json_eval('(8000, 8001)') == [8000, 8001]
+ assert json_eval('[8000, 8001]') == [8000, 8001]
+ assert json_eval('["abc", "def"]') == ['abc', 'def']
+ assert json_eval('{"foo": "bar"}') == {'foo': 'bar'}
+ assert json_eval('{"number": 1234}') == {'number': 1234}
# Hex string for peer_tos.
- self.assertEqual(json_eval('0x00'), '0x00')
- self.assertEqual(json_eval('1000'), 1000)
- self.assertEqual(json_eval('-6'), -6)
- self.assertEqual(json_eval('10.5'), 10.5)
- self.assertEqual(json_eval('True'), True)
- self.assertEqual(json_eval('false'), False)
- self.assertEqual(json_eval('none'), None)
+ assert json_eval('0x00') == '0x00'
+ assert json_eval('1000') == 1000
+ assert json_eval('-6') == -6
+ assert json_eval('10.5') == 10.5
+ assert json_eval('True')
+ assert not json_eval('false')
+ assert json_eval('none') is None
# Empty values to clear config key.
- self.assertEqual(json_eval('[]'), [])
- self.assertEqual(json_eval(''), '')
+ assert json_eval('[]') == []
+ assert json_eval('') == ''
diff --git a/deluge/tests/test_ui_entry.py b/deluge/tests/test_ui_entry.py
index f85bc7d7d..0546ad7b8 100644
--- a/deluge/tests/test_ui_entry.py
+++ b/deluge/tests/test_ui_entry.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,14 +6,13 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import argparse
import sys
from io import StringIO
+from unittest import mock
-import mock
import pytest
+import pytest_twisted
from twisted.internet import defer
import deluge
@@ -23,12 +21,12 @@ import deluge.ui.console
import deluge.ui.console.cmdline.commands.quit
import deluge.ui.console.main
import deluge.ui.web.server
-from deluge.common import PY2, get_localhost_auth, windows_check
+from deluge.common import get_localhost_auth, windows_check
+from deluge.conftest import BaseTestCase
from deluge.ui import ui_entry
from deluge.ui.web.server import DelugeWeb
from . import common
-from .basetest import BaseTestCase
from .daemon_base import DaemonBase
DEBUG_COMMAND = False
@@ -41,7 +39,7 @@ sys_stdout = sys.stdout
# To print to terminal from the tests, use: print('Message...', file=sys_stdout)
-class StringFileDescriptor(object):
+class StringFileDescriptor:
"""File descriptor that writes to string buffer"""
def __init__(self, fd):
@@ -51,22 +49,15 @@ class StringFileDescriptor(object):
setattr(self, a, getattr(sys_stdout, a))
def write(self, *data, **kwargs):
- # io.StringIO requires unicode strings.
data_string = str(*data)
- if PY2:
- data_string = data_string.decode()
print(data_string, file=self.out, end='')
def flush(self):
self.out.flush()
-class UIBaseTestCase(object):
- def __init__(self):
- self.var = {}
-
+class UIBaseTestCase:
def set_up(self):
- common.set_tmp_config_dir()
common.setup_test_logger(level='info', prefix=self.id())
return component.start()
@@ -82,28 +73,14 @@ class UIBaseTestCase(object):
class UIWithDaemonBaseTestCase(UIBaseTestCase, DaemonBase):
"""Subclass for test that require a deluged daemon"""
- def __init__(self):
- UIBaseTestCase.__init__(self)
-
def set_up(self):
d = self.common_set_up()
common.setup_test_logger(level='info', prefix=self.id())
- d.addCallback(self.start_core)
- return d
-
- def tear_down(self):
- d = UIBaseTestCase.tear_down(self)
- d.addCallback(self.terminate_core)
return d
-class DelugeEntryTestCase(BaseTestCase):
-
- if windows_check():
- skip = 'Console ui test on Windows broken due to sys args issue'
-
+class TestDelugeEntry(BaseTestCase):
def set_up(self):
- common.set_tmp_config_dir()
return component.start()
def tear_down(self):
@@ -119,10 +96,11 @@ class DelugeEntryTestCase(BaseTestCase):
self.patch(argparse._sys, 'stdout', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'):
- self.assertRaises(SystemExit, ui_entry.start_ui)
- self.assertTrue('usage: deluge' in fd.out.getvalue())
- self.assertTrue('UI Options:' in fd.out.getvalue())
- self.assertTrue('* console' in fd.out.getvalue())
+ with pytest.raises(SystemExit):
+ ui_entry.start_ui()
+ assert 'usage: deluge' in fd.out.getvalue()
+ assert 'UI Options:' in fd.out.getvalue()
+ assert '* console' in fd.out.getvalue()
def test_start_default(self):
self.patch(sys, 'argv', ['./deluge'])
@@ -157,7 +135,7 @@ class DelugeEntryTestCase(BaseTestCase):
# Just test that no exception is raised
ui_entry.start_ui()
- self.assertEqual(_level[0], 'info')
+ assert _level[0] == 'info'
class GtkUIBaseTestCase(UIBaseTestCase):
@@ -173,38 +151,27 @@ class GtkUIBaseTestCase(UIBaseTestCase):
@pytest.mark.gtkui
-class GtkUIDelugeScriptEntryTestCase(BaseTestCase, GtkUIBaseTestCase):
- def __init__(self, testname):
- super(GtkUIDelugeScriptEntryTestCase, self).__init__(testname)
- GtkUIBaseTestCase.__init__(self)
-
- self.var['cmd_name'] = 'deluge gtk'
- self.var['start_cmd'] = ui_entry.start_ui
- self.var['sys_arg_cmd'] = ['./deluge', 'gtk']
-
- def set_up(self):
- return GtkUIBaseTestCase.set_up(self)
-
- def tear_down(self):
- return GtkUIBaseTestCase.tear_down(self)
+class TestGtkUIDelugeScriptEntry(BaseTestCase, GtkUIBaseTestCase):
+ @pytest.fixture(autouse=True)
+ def set_var(self, request):
+ request.cls.var = {
+ 'cmd_name': 'deluge gtk',
+ 'start_cmd': ui_entry.start_ui,
+ 'sys_arg_cmd': ['./deluge', 'gtk'],
+ }
@pytest.mark.gtkui
-class GtkUIScriptEntryTestCase(BaseTestCase, GtkUIBaseTestCase):
- def __init__(self, testname):
- super(GtkUIScriptEntryTestCase, self).__init__(testname)
- GtkUIBaseTestCase.__init__(self)
+class TestGtkUIScriptEntry(BaseTestCase, GtkUIBaseTestCase):
+ @pytest.fixture(autouse=True)
+ def set_var(self, request):
from deluge.ui import gtk3
- self.var['cmd_name'] = 'deluge-gtk'
- self.var['start_cmd'] = gtk3.start
- self.var['sys_arg_cmd'] = ['./deluge-gtk']
-
- def set_up(self):
- return GtkUIBaseTestCase.set_up(self)
-
- def tear_down(self):
- return GtkUIBaseTestCase.tear_down(self)
+ request.cls.var = {
+ 'cmd_name': 'deluge-gtk',
+ 'start_cmd': gtk3.start,
+ 'sys_arg_cmd': ['./deluge-gtk'],
+ }
class DelugeWebMock(DelugeWeb):
@@ -242,45 +209,31 @@ class WebUIBaseTestCase(UIBaseTestCase):
self.patch(deluge.ui.web.server, 'DelugeWeb', DelugeWebMock)
self.exec_command()
- self.assertEqual(_level[0], 'info')
-
-
-class WebUIScriptEntryTestCase(BaseTestCase, WebUIBaseTestCase):
-
- if windows_check():
- skip = 'Console ui test on Windows broken due to sys args issue'
-
- def __init__(self, testname):
- super(WebUIScriptEntryTestCase, self).__init__(testname)
- WebUIBaseTestCase.__init__(self)
- self.var['cmd_name'] = 'deluge-web'
- self.var['start_cmd'] = deluge.ui.web.start
- self.var['sys_arg_cmd'] = ['./deluge-web', '--do-not-daemonize']
-
- def set_up(self):
- return WebUIBaseTestCase.set_up(self)
-
- def tear_down(self):
- return WebUIBaseTestCase.tear_down(self)
-
+ assert _level[0] == 'info'
-class WebUIDelugeScriptEntryTestCase(BaseTestCase, WebUIBaseTestCase):
- if windows_check():
- skip = 'Console ui test on Windows broken due to sys args issue'
+class TestWebUIScriptEntry(BaseTestCase, WebUIBaseTestCase):
+ @pytest.fixture(autouse=True)
+ def set_var(self, request):
+ request.cls.var = {
+ 'cmd_name': 'deluge-web',
+ 'start_cmd': deluge.ui.web.start,
+ 'sys_arg_cmd': ['./deluge-web'],
+ }
+ if not windows_check():
+ request.cls.var['sys_arg_cmd'].append('--do-not-daemonize')
- def __init__(self, testname):
- super(WebUIDelugeScriptEntryTestCase, self).__init__(testname)
- WebUIBaseTestCase.__init__(self)
- self.var['cmd_name'] = 'deluge web'
- self.var['start_cmd'] = ui_entry.start_ui
- self.var['sys_arg_cmd'] = ['./deluge', 'web', '--do-not-daemonize']
-
- def set_up(self):
- return WebUIBaseTestCase.set_up(self)
- def tear_down(self):
- return WebUIBaseTestCase.tear_down(self)
+class TestWebUIDelugeScriptEntry(BaseTestCase, WebUIBaseTestCase):
+ @pytest.fixture(autouse=True)
+ def set_var(self, request):
+ request.cls.var = {
+ 'cmd_name': 'deluge web',
+ 'start_cmd': ui_entry.start_ui,
+ 'sys_arg_cmd': ['./deluge', 'web'],
+ }
+ if not windows_check():
+ request.cls.var['sys_arg_cmd'].append('--do-not-daemonize')
class ConsoleUIBaseTestCase(UIBaseTestCase):
@@ -291,7 +244,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
with mock.patch('deluge.ui.console.main.ConsoleUI'):
self.exec_command()
- def test_start_console_with_log_level(self):
+ def test_start_console_with_log_level(self, request):
_level = []
def setup_logger(
@@ -314,7 +267,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
# Just test that no exception is raised
self.exec_command()
- self.assertEqual(_level[0], 'info')
+ assert _level[0] == 'info'
def test_console_help(self):
self.patch(sys, 'argv', self.var['sys_arg_cmd'] + ['-h'])
@@ -322,18 +275,19 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
self.patch(argparse._sys, 'stdout', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'):
- self.assertRaises(SystemExit, self.exec_command)
+ with pytest.raises(SystemExit):
+ self.exec_command()
std_output = fd.out.getvalue()
- self.assertTrue(
- ('usage: %s' % self.var['cmd_name']) in std_output
- ) # Check command name
- self.assertTrue('Common Options:' in std_output)
- self.assertTrue('Console Options:' in std_output)
- self.assertIn(
- 'Console Commands:\n The following console commands are available:',
- std_output,
+ assert (
+ 'usage: %s' % self.var['cmd_name']
+ ) in std_output # Check command name
+ assert 'Common Options:' in std_output
+ assert 'Console Options:' in std_output
+ assert (
+ 'Console Commands:\n The following console commands are available:'
+ in std_output
)
- self.assertIn('The following console commands are available:', std_output)
+ assert 'The following console commands are available:' in std_output
def test_console_command_info(self):
self.patch(sys, 'argv', self.var['sys_arg_cmd'] + ['info'])
@@ -349,10 +303,11 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
self.patch(argparse._sys, 'stdout', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'):
- self.assertRaises(SystemExit, self.exec_command)
+ with pytest.raises(SystemExit):
+ self.exec_command()
std_output = fd.out.getvalue()
- self.assertIn('usage: info', std_output)
- self.assertIn('Show information about the torrents', std_output)
+ assert 'usage: info' in std_output
+ assert 'Show information about the torrents' in std_output
def test_console_unrecognized_arguments(self):
self.patch(
@@ -361,8 +316,9 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
fd = StringFileDescriptor(sys.stdout)
self.patch(argparse._sys, 'stderr', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'):
- self.assertRaises(SystemExit, self.exec_command)
- self.assertIn('unrecognized arguments: --ui', fd.out.getvalue())
+ with pytest.raises(SystemExit):
+ self.exec_command()
+ assert 'unrecognized arguments: --ui' in fd.out.getvalue()
class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase):
@@ -390,26 +346,28 @@ class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase):
+ command,
)
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_console_command_add(self):
filename = common.get_test_data_file('test.torrent')
- self.patch_arg_command(['add ' + filename])
+ self.patch_arg_command([f'add "{filename}"'])
fd = StringFileDescriptor(sys.stdout)
self.patch(sys, 'stdout', fd)
yield self.exec_command()
std_output = fd.out.getvalue()
- self.assertEqual(
- std_output, 'Attempting to add torrent: ' + filename + '\nTorrent added!\n'
+ assert (
+ std_output
+ == 'Attempting to add torrent: ' + filename + '\nTorrent added!\n'
)
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_console_command_add_move_completed(self):
filename = common.get_test_data_file('test.torrent')
+ tmp_path = 'c:\\tmp' if windows_check() else '/tmp'
self.patch_arg_command(
[
- 'add --move-path /tmp ' + filename + ' ; status'
+ f'add --move-path "{tmp_path}" "{filename}" ; status'
' ; manage'
' ab570cdd5a17ea1b61e970bb72047de141bce173'
' move_completed'
@@ -422,22 +380,23 @@ class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase):
yield self.exec_command()
std_output = fd.out.getvalue()
- self.assertTrue(
- std_output.endswith('move_completed: True\nmove_completed_path: /tmp\n')
- or std_output.endswith('move_completed_path: /tmp\nmove_completed: True\n')
+ assert std_output.endswith(
+ f'move_completed: True\nmove_completed_path: {tmp_path}\n'
+ ) or std_output.endswith(
+ f'move_completed_path: {tmp_path}\nmove_completed: True\n'
)
- @defer.inlineCallbacks
- def test_console_command_status(self):
+ @pytest_twisted.ensureDeferred
+ async def test_console_command_status(self):
fd = StringFileDescriptor(sys.stdout)
self.patch_arg_command(['status'])
self.patch(sys, 'stdout', fd)
- yield self.exec_command()
+ await self.exec_command()
std_output = fd.out.getvalue()
- self.assertTrue(std_output.startswith('Total upload: '))
- self.assertTrue(std_output.endswith(' Moving: 0\n'))
+ assert std_output.startswith('Total upload: ')
+ assert std_output.endswith(' Moving: 0\n')
@defer.inlineCallbacks
def test_console_command_config_set_download_location(self):
@@ -447,79 +406,36 @@ class ConsoleUIWithDaemonBaseTestCase(UIWithDaemonBaseTestCase):
yield self.exec_command()
std_output = fd.out.getvalue()
- self.assertTrue(
- std_output.startswith(
- 'Setting "download_location" to: {}\'/downloads\''.format(
- 'u' if PY2 else ''
- )
- )
- )
- self.assertTrue(
- std_output.endswith('Configuration value successfully updated.\n')
- )
-
-
-class ConsoleScriptEntryWithDaemonTestCase(
- BaseTestCase, ConsoleUIWithDaemonBaseTestCase
-):
-
- if windows_check():
- skip = 'Console ui test on Windows broken due to sys args issue'
-
- def __init__(self, testname):
- super(ConsoleScriptEntryWithDaemonTestCase, self).__init__(testname)
- ConsoleUIWithDaemonBaseTestCase.__init__(self)
- self.var['cmd_name'] = 'deluge-console'
- self.var['sys_arg_cmd'] = ['./deluge-console']
-
- def set_up(self):
- from deluge.ui.console.console import Console
-
- def start_console():
- return Console().start()
-
- self.patch(deluge.ui.console, 'start', start_console)
- self.var['start_cmd'] = deluge.ui.console.start
-
- return ConsoleUIWithDaemonBaseTestCase.set_up(self)
-
- def tear_down(self):
- return ConsoleUIWithDaemonBaseTestCase.tear_down(self)
-
-
-class ConsoleScriptEntryTestCase(BaseTestCase, ConsoleUIBaseTestCase):
-
- if windows_check():
- skip = 'Console ui test on Windows broken due to sys args issue'
-
- def __init__(self, testname):
- super(ConsoleScriptEntryTestCase, self).__init__(testname)
- ConsoleUIBaseTestCase.__init__(self)
- self.var['cmd_name'] = 'deluge-console'
- self.var['start_cmd'] = deluge.ui.console.start
- self.var['sys_arg_cmd'] = ['./deluge-console']
-
- def set_up(self):
- return ConsoleUIBaseTestCase.set_up(self)
-
- def tear_down(self):
- return ConsoleUIBaseTestCase.tear_down(self)
-
-
-class ConsoleDelugeScriptEntryTestCase(BaseTestCase, ConsoleUIBaseTestCase):
-
- if windows_check():
- skip = 'cannot test console ui on windows'
-
- def __init__(self, testname):
- super(ConsoleDelugeScriptEntryTestCase, self).__init__(testname)
- ConsoleUIBaseTestCase.__init__(self)
- self.var['cmd_name'] = 'deluge console'
- self.var['start_cmd'] = ui_entry.start_ui
- self.var['sys_arg_cmd'] = ['./deluge', 'console']
-
- def set_up(self):
- return ConsoleUIBaseTestCase.set_up(self)
-
- def tear_down(self):
- return ConsoleUIBaseTestCase.tear_down(self)
+ assert std_output.startswith('Setting "download_location" to: \'/downloads\'')
+ assert std_output.endswith('Configuration value successfully updated.\n')
+
+
+@pytest.mark.usefixtures('daemon', 'client')
+class TestConsoleScriptEntryWithDaemon(BaseTestCase, ConsoleUIWithDaemonBaseTestCase):
+ @pytest.fixture(autouse=True)
+ def set_var(self, request):
+ request.cls.var = {
+ 'cmd_name': 'deluge-console',
+ 'start_cmd': deluge.ui.console.start,
+ 'sys_arg_cmd': ['./deluge-console'],
+ }
+
+
+class TestConsoleScriptEntry(BaseTestCase, ConsoleUIBaseTestCase):
+ @pytest.fixture(autouse=True)
+ def set_var(self, request):
+ request.cls.var = {
+ 'cmd_name': 'deluge-console',
+ 'start_cmd': deluge.ui.console.start,
+ 'sys_arg_cmd': ['./deluge-console'],
+ }
+
+
+class TestConsoleDelugeScriptEntry(BaseTestCase, ConsoleUIBaseTestCase):
+ @pytest.fixture(autouse=True)
+ def set_var(self, request):
+ request.cls.var = {
+ 'cmd_name': 'deluge console',
+ 'start_cmd': ui_entry.start_ui,
+ 'sys_arg_cmd': ['./deluge', 'console'],
+ }
diff --git a/deluge/tests/test_ui_gtk3.py b/deluge/tests/test_ui_gtk3.py
index a208bb494..e6d025c7c 100644
--- a/deluge/tests/test_ui_gtk3.py
+++ b/deluge/tests/test_ui_gtk3.py
@@ -1,21 +1,17 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import sys
+from unittest import mock
-import mock
import pytest
-from twisted.trial import unittest
@pytest.mark.gtkui
-class GTK3CommonTestCase(unittest.TestCase):
+class TestGTK3Common:
def setUp(self):
sys.modules['gi.repository'] = mock.MagicMock()
@@ -25,10 +21,10 @@ class GTK3CommonTestCase(unittest.TestCase):
def test_cmp(self):
from deluge.ui.gtk3.common import cmp
- self.assertEqual(cmp(None, None), 0)
- self.assertEqual(cmp(1, None), 1)
- self.assertEqual(cmp(0, None), 1)
- self.assertEqual(cmp(None, 7), -1)
- self.assertEqual(cmp(None, 'bar'), -1)
- self.assertEqual(cmp('foo', None), 1)
- self.assertEqual(cmp('', None), 1)
+ assert cmp(None, None) == 0
+ assert cmp(1, None) == 1
+ assert cmp(0, None) == 1
+ assert cmp(None, 7) == -1
+ assert cmp(None, 'bar') == -1
+ assert cmp('foo', None) == 1
+ assert cmp('', None) == 1
diff --git a/deluge/tests/test_web_api.py b/deluge/tests/test_web_api.py
index 0180e0bda..56f86aa56 100644
--- a/deluge/tests/test_web_api.py
+++ b/deluge/tests/test_web_api.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,19 +6,17 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import json
from io import BytesIO
+import pytest
+import pytest_twisted
from twisted.internet import defer, reactor
-from twisted.python.failure import Failure
from twisted.web.client import Agent, FileBodyProducer
from twisted.web.http_headers import Headers
from twisted.web.static import File
import deluge.component as component
-from deluge.ui.client import client
from . import common
from .common_web import WebServerTestBase
@@ -27,20 +24,19 @@ from .common_web import WebServerTestBase
common.disable_new_release_check()
-class WebAPITestCase(WebServerTestBase):
- def test_connect_invalid_host(self):
- d = self.deluge_web.web_api.connect('id')
- d.addCallback(self.fail)
- d.addErrback(self.assertIsInstance, Failure)
- return d
+class TestWebAPI(WebServerTestBase):
+ @pytest.mark.xfail(reason='This just logs an error at the moment.')
+ @pytest_twisted.ensureDeferred
+ async def test_connect_invalid_host(self):
+ with pytest.raises(Exception):
+ await self.deluge_web.web_api.connect('id')
- def test_connect(self):
+ def test_connect(self, client):
d = self.deluge_web.web_api.connect(self.host_id)
def on_connect(result):
- self.assertEqual(type(result), tuple)
- self.assertTrue(len(result) > 0)
- self.addCleanup(client.disconnect)
+ assert type(result) == tuple
+ assert len(result) > 0
return result
d.addCallback(on_connect)
@@ -52,9 +48,9 @@ class WebAPITestCase(WebServerTestBase):
@defer.inlineCallbacks
def on_connect(result):
- self.assertTrue(self.deluge_web.web_api.connected())
+ assert self.deluge_web.web_api.connected()
yield self.deluge_web.web_api.disconnect()
- self.assertFalse(self.deluge_web.web_api.connected())
+ assert not self.deluge_web.web_api.connected()
d.addCallback(on_connect)
d.addErrback(self.fail)
@@ -62,7 +58,7 @@ class WebAPITestCase(WebServerTestBase):
def test_get_config(self):
config = self.deluge_web.web_api.get_config()
- self.assertEqual(self.webserver_listen_port, config['port'])
+ assert self.webserver_listen_port == config['port']
def test_set_config(self):
config = self.deluge_web.web_api.get_config()
@@ -77,9 +73,9 @@ class WebAPITestCase(WebServerTestBase):
}
self.deluge_web.web_api.set_config(config)
web_config = component.get('DelugeWeb').config.config
- self.assertNotEquals(config['pwd_salt'], web_config['pwd_salt'])
- self.assertNotEquals(config['pwd_sha1'], web_config['pwd_sha1'])
- self.assertNotEquals(config['sessions'], web_config['sessions'])
+ assert config['pwd_salt'] != web_config['pwd_salt']
+ assert config['pwd_sha1'] != web_config['pwd_sha1']
+ assert config['sessions'] != web_config['sessions']
@defer.inlineCallbacks
def get_host_status(self):
@@ -87,49 +83,49 @@ class WebAPITestCase(WebServerTestBase):
host[3] = 'Online'
host[4] = '2.0.0.dev562'
status = yield self.deluge_web.web_api.get_host_status(self.host_id)
- self.assertEqual(status, tuple(status))
+ assert status == tuple(status)
def test_get_host(self):
- self.assertFalse(self.deluge_web.web_api._get_host('invalid_id'))
+ assert not self.deluge_web.web_api._get_host('invalid_id')
conn = list(self.deluge_web.web_api.hostlist.get_hosts_info()[0])
- self.assertEqual(self.deluge_web.web_api._get_host(conn[0]), conn[0:4])
+ assert self.deluge_web.web_api._get_host(conn[0]) == conn[0:4]
def test_add_host(self):
conn = ['abcdef', '10.0.0.1', 0, 'user123', 'pass123']
- self.assertFalse(self.deluge_web.web_api._get_host(conn[0]))
+ assert not self.deluge_web.web_api._get_host(conn[0])
# Add valid host
result, host_id = self.deluge_web.web_api.add_host(
conn[1], conn[2], conn[3], conn[4]
)
- self.assertEqual(result, True)
+ assert result
conn[0] = host_id
- self.assertEqual(self.deluge_web.web_api._get_host(conn[0]), conn[0:4])
+ assert self.deluge_web.web_api._get_host(conn[0]) == conn[0:4]
# Add already existing host
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4])
- self.assertEqual(ret, (False, 'Host details already in hostlist'))
+ assert ret == (False, 'Host details already in hostlist')
# Add invalid port
conn[2] = 'bad port'
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4])
- self.assertEqual(ret, (False, 'Invalid port. Must be an integer'))
+ assert ret == (False, 'Invalid port. Must be an integer')
def test_remove_host(self):
conn = ['connection_id', '', 0, '', '']
self.deluge_web.web_api.hostlist.config['hosts'].append(conn)
- self.assertEqual(self.deluge_web.web_api._get_host(conn[0]), conn[0:4])
+ assert self.deluge_web.web_api._get_host(conn[0]) == conn[0:4]
# Remove valid host
- self.assertTrue(self.deluge_web.web_api.remove_host(conn[0]))
- self.assertFalse(self.deluge_web.web_api._get_host(conn[0]))
+ assert self.deluge_web.web_api.remove_host(conn[0])
+ assert not self.deluge_web.web_api._get_host(conn[0])
# Remove non-existing host
- self.assertFalse(self.deluge_web.web_api.remove_host(conn[0]))
+ assert not self.deluge_web.web_api.remove_host(conn[0])
def test_get_torrent_info(self):
filename = common.get_test_data_file('test.torrent')
ret = self.deluge_web.web_api.get_torrent_info(filename)
- self.assertEqual(ret['name'], 'azcvsupdater_2.6.2.jar')
- self.assertEqual(ret['info_hash'], 'ab570cdd5a17ea1b61e970bb72047de141bce173')
- self.assertTrue('files_tree' in ret)
+ assert ret['name'] == 'azcvsupdater_2.6.2.jar'
+ assert ret['info_hash'] == 'ab570cdd5a17ea1b61e970bb72047de141bce173'
+ assert 'files_tree' in ret
def test_get_torrent_info_with_md5(self):
filename = common.get_test_data_file('md5sum.torrent')
@@ -137,19 +133,19 @@ class WebAPITestCase(WebServerTestBase):
# JSON dumping happens during response creation in normal usage
# JSON serialization may fail if any of the dictionary items are byte arrays rather than strings
ret = json.loads(json.dumps(ret))
- self.assertEqual(ret['name'], 'test')
- self.assertEqual(ret['info_hash'], 'f6408ba9944cf9fe01b547b28f336b3ee6ec32c5')
- self.assertTrue('files_tree' in ret)
+ assert ret['name'] == 'test'
+ assert ret['info_hash'] == 'f6408ba9944cf9fe01b547b28f336b3ee6ec32c5'
+ assert 'files_tree' in ret
def test_get_magnet_info(self):
ret = self.deluge_web.web_api.get_magnet_info(
'magnet:?xt=urn:btih:SU5225URMTUEQLDXQWRB2EQWN6KLTYKN'
)
- self.assertEqual(ret['name'], '953bad769164e8482c7785a21d12166f94b9e14d')
- self.assertEqual(ret['info_hash'], '953bad769164e8482c7785a21d12166f94b9e14d')
- self.assertTrue('files_tree' in ret)
+ assert ret['name'] == '953bad769164e8482c7785a21d12166f94b9e14d'
+ assert ret['info_hash'] == '953bad769164e8482c7785a21d12166f94b9e14d'
+ assert 'files_tree' in ret
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_get_torrent_files(self):
yield self.deluge_web.web_api.connect(self.host_id)
filename = common.get_test_data_file('test.torrent')
@@ -160,23 +156,20 @@ class WebAPITestCase(WebServerTestBase):
ret = yield self.deluge_web.web_api.get_torrent_files(
'ab570cdd5a17ea1b61e970bb72047de141bce173'
)
- self.assertEqual(ret['type'], 'dir')
- self.assertEqual(
- ret['contents'],
- {
- 'azcvsupdater_2.6.2.jar': {
- 'priority': 4,
- 'index': 0,
- 'offset': 0,
- 'progress': 0.0,
- 'path': 'azcvsupdater_2.6.2.jar',
- 'type': 'file',
- 'size': 307949,
- }
- },
- )
+ assert ret['type'] == 'dir'
+ assert ret['contents'] == {
+ 'azcvsupdater_2.6.2.jar': {
+ 'priority': 4,
+ 'index': 0,
+ 'offset': 0,
+ 'progress': 0.0,
+ 'path': 'azcvsupdater_2.6.2.jar',
+ 'type': 'file',
+ 'size': 307949,
+ }
+ }
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_download_torrent_from_url(self):
filename = 'ubuntu-9.04-desktop-i386.iso.torrent'
self.deluge_web.top_level.putChild(
@@ -184,9 +177,9 @@ class WebAPITestCase(WebServerTestBase):
)
url = 'http://localhost:%d/%s' % (self.webserver_listen_port, filename)
res = yield self.deluge_web.web_api.download_torrent_from_url(url)
- self.assertTrue(res.endswith(filename))
+ assert res.endswith(filename)
- @defer.inlineCallbacks
+ @pytest_twisted.inlineCallbacks
def test_invalid_json(self):
"""
If json_api._send_response does not return server.NOT_DONE_YET
diff --git a/deluge/tests/test_web_auth.py b/deluge/tests/test_web_auth.py
index a5185737c..39d66c1c1 100644
--- a/deluge/tests/test_web_auth.py
+++ b/deluge/tests/test_web_auth.py
@@ -1,18 +1,15 @@
-# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-from mock import patch
-from twisted.trial import unittest
+from unittest.mock import patch
from deluge.ui.web import auth
-class MockConfig(object):
+class MockConfig:
def __init__(self, config):
self.config = config
@@ -23,7 +20,7 @@ class MockConfig(object):
self.config[key] = value
-class WebAuthTestCase(unittest.TestCase):
+class TestWebAuth:
@patch('deluge.ui.web.auth.JSONComponent.__init__', return_value=None)
def test_change_password(self, mock_json):
config = MockConfig(
@@ -33,4 +30,4 @@ class WebAuthTestCase(unittest.TestCase):
}
)
webauth = auth.Auth(config)
- self.assertTrue(webauth.change_password('deluge', 'deluge_new'))
+ assert webauth.change_password('deluge', 'deluge_new')
diff --git a/deluge/tests/test_webserver.py b/deluge/tests/test_webserver.py
index d9684bacd..e1588fdf3 100644
--- a/deluge/tests/test_webserver.py
+++ b/deluge/tests/test_webserver.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,13 +6,12 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import json as json_lib
from io import BytesIO
+import pytest_twisted
import twisted.web.client
-from twisted.internet import defer, reactor
+from twisted.internet import reactor
from twisted.web.client import Agent, FileBodyProducer
from twisted.web.http_headers import Headers
@@ -24,8 +22,8 @@ from .common_web import WebServerMockBase, WebServerTestBase
common.disable_new_release_check()
-class WebServerTestCase(WebServerTestBase, WebServerMockBase):
- @defer.inlineCallbacks
+class TestWebServer(WebServerTestBase, WebServerMockBase):
+ @pytest_twisted.inlineCallbacks
def test_get_torrent_info(self):
agent = Agent(reactor)
@@ -37,7 +35,8 @@ class WebServerTestCase(WebServerTestBase, WebServerMockBase):
# UnicodeDecodeError: 'utf8' codec can't decode byte 0xe5 in position 0: invalid continuation byte
filename = get_test_data_file('filehash_field.torrent')
input_file = (
- '{"params": ["%s"], "method": "web.get_torrent_info", "id": 22}' % filename
+ '{"params": ["%s"], "method": "web.get_torrent_info", "id": 22}'
+ % filename.replace('\\', '\\\\')
)
headers = {
b'User-Agent': ['Twisted Web Client Example'],
@@ -51,9 +50,11 @@ class WebServerTestCase(WebServerTestBase, WebServerMockBase):
Headers(headers),
FileBodyProducer(BytesIO(input_file.encode('utf-8'))),
)
-
body = yield twisted.web.client.readBody(d)
- json = json_lib.loads(body.decode())
- self.assertEqual(None, json['error'])
- self.assertEqual('torrent_filehash', json['result']['name'])
+ try:
+ json = json_lib.loads(body.decode())
+ except Exception:
+ print('aoeu')
+ assert json['error'] is None
+ assert 'torrent_filehash' == json['result']['name']
diff --git a/deluge/tests/twisted/plugins/delugereporter.py b/deluge/tests/twisted/plugins/delugereporter.py
deleted file mode 100644
index c2a7b52b5..000000000
--- a/deluge/tests/twisted/plugins/delugereporter.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
-# the additional special exception to link portions of this program with the OpenSSL library.
-# See LICENSE for more details.
-#
-
-from __future__ import unicode_literals
-
-import os
-
-from twisted.plugin import IPlugin
-from twisted.trial.itrial import IReporter
-from twisted.trial.reporter import TreeReporter
-from zope.interface import implements
-
-
-class _Reporter(object):
- implements(IPlugin, IReporter)
-
- def __init__(
- self, name, module, description, longOpt, shortOpt, klass # noqa: N803
- ):
- self.name = name
- self.module = module
- self.description = description
- self.longOpt = longOpt
- self.shortOpt = shortOpt
- self.klass = klass
-
-
-deluge = _Reporter(
- 'Deluge reporter that suppresses Stacktrace from TODO tests',
- 'twisted.plugins.delugereporter',
- description='Deluge Reporter',
- longOpt='deluge-reporter',
- shortOpt=None,
- klass='DelugeReporter',
-)
-
-
-class DelugeReporter(TreeReporter):
- def __init__(self, *args, **kwargs):
- os.environ['DELUGE_REPORTER'] = 'true'
- TreeReporter.__init__(self, *args, **kwargs)
-
- def addExpectedFailure(self, *args): # NOQA: N802
- # super(TreeReporter, self).addExpectedFailure(*args)
- self.endLine('[TODO]', self.TODO)
diff --git a/deluge/transfer.py b/deluge/transfer.py
index 6f8884ad4..ed7d6dd9a 100644
--- a/deluge/transfer.py
+++ b/deluge/transfer.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 Bro <bro.development@gmail.com>
# Copyright (C) 2018 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import struct
import zlib
@@ -24,7 +21,7 @@ MESSAGE_HEADER_FORMAT = '!BI'
MESSAGE_HEADER_SIZE = struct.calcsize(MESSAGE_HEADER_FORMAT)
-class DelugeTransferProtocol(Protocol, object):
+class DelugeTransferProtocol(Protocol):
"""
Deluge RPC wire protocol.
@@ -56,7 +53,7 @@ class DelugeTransferProtocol(Protocol, object):
body = zlib.compress(rencode.dumps(data))
body_len = len(body)
message = struct.pack(
- '{}{}s'.format(MESSAGE_HEADER_FORMAT, body_len),
+ f'{MESSAGE_HEADER_FORMAT}{body_len}s',
PROTOCOL_VERSION,
body_len,
body,
diff --git a/deluge/ui/client.py b/deluge/ui/client.py
index 180d8ef2b..6b657d5ca 100644
--- a/deluge/ui/client.py
+++ b/deluge/ui/client.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import subprocess
import sys
@@ -33,7 +30,7 @@ def format_kwargs(kwargs):
return ', '.join([key + '=' + str(value) for key, value in kwargs.items()])
-class DelugeRPCRequest(object):
+class DelugeRPCRequest:
"""
This object is created whenever there is a RPCRequest to be sent to the
daemon. It is generally only used by the DaemonProxy's call method.
@@ -243,7 +240,7 @@ class DelugeRPCClientFactory(ClientFactory):
self.daemon.disconnect_callback()
-class DaemonProxy(object):
+class DaemonProxy:
pass
@@ -526,7 +523,7 @@ class DaemonStandaloneProxy(DaemonProxy):
self.__daemon.core.eventmanager.deregister_event_handler(event, handler)
-class DottedObject(object):
+class DottedObject:
"""
This is used for dotted name calls to client
"""
@@ -551,7 +548,7 @@ class RemoteMethod(DottedObject):
return self.daemon.call(self.base, *args, **kwargs)
-class Client(object):
+class Client:
"""
This class is used to connect to a daemon process and issue RPC requests.
"""
@@ -615,7 +612,7 @@ class Client(object):
d.addErrback(on_authenticate_fail)
return d
- d.addCallback(on_connected)
+ d.addCallbacks(on_connected)
d.addErrback(on_connect_fail)
if not skip_authentication:
d.addCallback(authenticate, username, password)
diff --git a/deluge/ui/common.py b/deluge/ui/common.py
index c5064a6f4..f9f774e23 100644
--- a/deluge/ui/common.py
+++ b/deluge/ui/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) Damien Churchill 2008-2009 <damoxc@gmail.com>
# Copyright (C) Andrew Resch 2009 <andrewresch@gmail.com>
@@ -11,8 +10,6 @@
"""
The ui common module contains methods and classes that are deemed useful for all the interfaces.
"""
-from __future__ import unicode_literals
-
import logging
import os
from hashlib import sha1 as sha
@@ -167,7 +164,7 @@ DISK_CACHE_KEYS = [
]
-class TorrentInfo(object):
+class TorrentInfo:
"""Collects information about a torrent file.
Args:
@@ -186,7 +183,7 @@ class TorrentInfo(object):
try:
with open(filename, 'rb') as _file:
self._filedata = _file.read()
- except IOError as ex:
+ except OSError as ex:
log.warning('Unable to open %s: %s', filename, ex)
return
@@ -387,7 +384,7 @@ class TorrentInfo(object):
return self._filedata
-class FileTree2(object):
+class FileTree2:
"""
Converts a list of paths in to a file tree.
@@ -467,7 +464,7 @@ class FileTree2(object):
return '\n'.join(lines)
-class FileTree(object):
+class FileTree:
"""
Convert a list of paths in a file tree.
diff --git a/deluge/ui/console/__init__.py b/deluge/ui/console/__init__.py
index 56e8d629d..7da04a6de 100644
--- a/deluge/ui/console/__init__.py
+++ b/deluge/ui/console/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.ui.console.console import Console
UI_PATH = __path__[0]
@@ -16,4 +13,4 @@ UI_PATH = __path__[0]
def start():
- Console().start()
+ return Console().start()
diff --git a/deluge/ui/console/cmdline/command.py b/deluge/ui/console/cmdline/command.py
index 2ff32dff9..40edd78f0 100644
--- a/deluge/ui/console/cmdline/command.py
+++ b/deluge/ui/console/cmdline/command.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -9,8 +8,6 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import logging
import shlex
@@ -23,7 +20,7 @@ from deluge.ui.console.utils.colors import strip_colors
log = logging.getLogger(__name__)
-class Commander(object):
+class Commander:
def __init__(self, cmds, interactive=False):
self._commands = cmds
self.interactive = interactive
@@ -144,7 +141,7 @@ class Commander(object):
return ret
-class BaseCommand(object):
+class BaseCommand:
usage = None
interactive_only = False
diff --git a/deluge/ui/console/cmdline/commands/__init__.py b/deluge/ui/console/cmdline/commands/__init__.py
index 628fae597..39dbefe2a 100644
--- a/deluge/ui/console/cmdline/commands/__init__.py
+++ b/deluge/ui/console/cmdline/commands/__init__.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
from deluge.ui.console.cmdline.command import BaseCommand
__all__ = ['BaseCommand']
diff --git a/deluge/ui/console/cmdline/commands/add.py b/deluge/ui/console/cmdline/commands/add.py
index da42695b5..706ae168e 100644
--- a/deluge/ui/console/cmdline/commands/add.py
+++ b/deluge/ui/console/cmdline/commands/add.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,10 +7,10 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import os
from base64 import b64encode
+from urllib.parse import urlparse
+from urllib.request import url2pathname
from twisted.internet import defer
@@ -21,14 +20,6 @@ from deluge.ui.client import client
from . import BaseCommand
-try:
- from urllib.parse import urlparse
- from urllib.request import url2pathname
-except ImportError:
- # PY2 fallback
- from urllib import url2pathname # pylint: disable=ungrouped-imports
- from urlparse import urlparse # pylint: disable=ungrouped-imports
-
class Command(BaseCommand):
"""Add torrents"""
diff --git a/deluge/ui/console/cmdline/commands/cache.py b/deluge/ui/console/cmdline/commands/cache.py
index e427f085f..fe6cd580d 100644
--- a/deluge/ui/console/cmdline/commands/cache.py
+++ b/deluge/ui/console/cmdline/commands/cache.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from deluge.ui.client import client
from deluge.ui.common import DISK_CACHE_KEYS
@@ -24,7 +21,7 @@ class Command(BaseCommand):
def on_cache_status(status):
for key, value in sorted(status.items()):
- self.console.write('{!info!}%s: {!input!}%s' % (key, value))
+ self.console.write(f'{{!info!}}{key}: {{!input!}}{value}')
return client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
on_cache_status
diff --git a/deluge/ui/console/cmdline/commands/config.py b/deluge/ui/console/cmdline/commands/config.py
index 9821e47bc..8b31ca3cd 100644
--- a/deluge/ui/console/cmdline/commands/config.py
+++ b/deluge/ui/console/cmdline/commands/config.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import json
import logging
import re
@@ -97,10 +94,10 @@ class Command(BaseCommand):
value = pprint.pformat(value, 2, 80)
new_value = []
for line in value.splitlines():
- new_value.append('%s%s' % (color, line))
+ new_value.append(f'{color}{line}')
value = '\n'.join(new_value)
- string += '%s: %s%s\n' % (key, color, value)
+ string += f'{key}: {color}{value}\n'
self.console.write(string.strip())
return client.core.get_config().addCallback(_on_get_config)
@@ -132,7 +129,7 @@ class Command(BaseCommand):
def on_set_config(result):
self.console.write('{!success!}Configuration value successfully updated.')
- self.console.write('Setting "%s" to: %r' % (key, val))
+ self.console.write(f'Setting "{key}" to: {val!r}')
return client.core.set_config({key: val}).addCallback(on_set_config)
def complete(self, text):
diff --git a/deluge/ui/console/cmdline/commands/connect.py b/deluge/ui/console/cmdline/commands/connect.py
index 6588f7a04..4c76de38f 100644
--- a/deluge/ui/console/cmdline/commands/connect.py
+++ b/deluge/ui/console/cmdline/commands/connect.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
@@ -57,17 +54,12 @@ class Command(BaseCommand):
def on_connect(result):
if self.console.interactive:
- self.console.write('{!success!}Connected to %s:%s!' % (host, port))
+ self.console.write(f'{{!success!}}Connected to {host}:{port}!')
return component.start()
def on_connect_fail(result):
- try:
- msg = result.value.exception_msg
- except AttributeError:
- msg = result.value.message
self.console.write(
- '{!error!}Failed to connect to %s:%s with reason: %s'
- % (host, port, msg)
+ f'{{!error!}}Failed to connect to {host}:{port} with reason: {result.value.message}'
)
return result
diff --git a/deluge/ui/console/cmdline/commands/debug.py b/deluge/ui/console/cmdline/commands/debug.py
index 3ca06ed15..af48a8b7f 100644
--- a/deluge/ui/console/cmdline/commands/debug.py
+++ b/deluge/ui/console/cmdline/commands/debug.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from twisted.internet import defer
import deluge.component as component
diff --git a/deluge/ui/console/cmdline/commands/gui.py b/deluge/ui/console/cmdline/commands/gui.py
index 10e4c499b..575bc9b3a 100644
--- a/deluge/ui/console/cmdline/commands/gui.py
+++ b/deluge/ui/console/cmdline/commands/gui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
diff --git a/deluge/ui/console/cmdline/commands/halt.py b/deluge/ui/console/cmdline/commands/halt.py
index 635595898..608f2de9d 100644
--- a/deluge/ui/console/cmdline/commands/halt.py
+++ b/deluge/ui/console/cmdline/commands/halt.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from deluge.ui.client import client
diff --git a/deluge/ui/console/cmdline/commands/help.py b/deluge/ui/console/cmdline/commands/help.py
index 2711eea99..754dadbec 100644
--- a/deluge/ui/console/cmdline/commands/help.py
+++ b/deluge/ui/console/cmdline/commands/help.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from twisted.internet import defer
diff --git a/deluge/ui/console/cmdline/commands/info.py b/deluge/ui/console/cmdline/commands/info.py
index 0d22f76a9..7ea9a6773 100644
--- a/deluge/ui/console/cmdline/commands/info.py
+++ b/deluge/ui/console/cmdline/commands/info.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
from os.path import sep as dirsep
import deluge.component as component
@@ -70,6 +67,7 @@ STATUS_KEYS = [
'total_payload_download',
'total_payload_upload',
'time_added',
+ 'label',
]
# Add filter specific state to torrent states
@@ -177,7 +175,7 @@ class Command(BaseCommand):
sort_key = 'name'
sort_reverse = False
for key, value in sorted(
- list(status.items()),
+ status.items(),
key=lambda x: x[1].get(sort_key),
reverse=sort_reverse,
):
@@ -218,9 +216,9 @@ class Command(BaseCommand):
for depth, subdir in enumerate(filepath):
indent = ' ' * depth * spaces_per_level
if depth >= len(prevpath):
- self.console.write('%s{!cyan!}%s' % (indent, subdir))
+ self.console.write(f'{indent}{{!cyan!}}{subdir}')
elif subdir != prevpath[depth]:
- self.console.write('%s{!cyan!}%s' % (indent, subdir))
+ self.console.write(f'{indent}{{!cyan!}}{subdir}')
depth = len(filepath)
@@ -296,7 +294,7 @@ class Command(BaseCommand):
s += peer['ip']
else:
# IPv6
- s += '[%s]:%s' % (
+ s += '[{}]:{}'.format(
':'.join(peer['ip'].split(':')[:-1]),
peer['ip'].split(':')[-1],
)
@@ -308,7 +306,7 @@ class Command(BaseCommand):
s += '\t\t'
else:
s += '\t'
- s += '%s%s\t%s%s' % (
+ s += '{}{}\t{}{}'.format(
colors.state_color['Seeding'],
fspeed(peer['up_speed']),
colors.state_color['Downloading'],
@@ -336,7 +334,7 @@ class Command(BaseCommand):
if verbose or detailed:
self.console.write('{!info!}Name: {!input!}%s' % (status['name']))
self.console.write('{!info!}ID: {!input!}%s' % (torrent_id))
- s = '{!info!}State: %s%s' % (
+ s = '{{!info!}}State: {}{}'.format(
colors.state_color[status['state']],
status['state'],
)
@@ -354,12 +352,12 @@ class Command(BaseCommand):
self.console.write(s)
if status['state'] in ('Seeding', 'Downloading', 'Queued'):
- s = '{!info!}Seeds: {!input!}%s (%s)' % (
+ s = '{{!info!}}Seeds: {{!input!}}{} ({})'.format(
status['num_seeds'],
status['total_seeds'],
)
s += sep
- s += '{!info!}Peers: {!input!}%s (%s)' % (
+ s += '{{!info!}}Peers: {{!input!}}{} ({})'.format(
status['num_peers'],
status['total_peers'],
)
@@ -378,7 +376,7 @@ class Command(BaseCommand):
if total_done == total_size:
s = '{!info!}Size: {!input!}%s' % (total_size)
else:
- s = '{!info!}Size: {!input!}%s/%s' % (total_done, total_size)
+ s = f'{{!info!}}Size: {{!input!}}{total_done}/{total_size}'
s += sep
s += '{!info!}Downloaded: {!input!}%s' % fsize(
status['all_time_download'], shortform=True
@@ -418,14 +416,20 @@ class Command(BaseCommand):
pbar = f_progressbar(
status['progress'], cols - (13 + len('%.2f%%' % status['progress']))
)
- s = '{!info!}Progress: {!input!}%.2f%% %s' % (status['progress'], pbar)
+ s = '{{!info!}}Progress: {{!input!}}{:.2f}% {}'.format(
+ status['progress'], pbar
+ )
self.console.write(s)
s = '{!info!}Download Folder: {!input!}%s' % status['download_location']
- self.console.write(s + '\n')
+ self.console.write(s)
+
+ if 'label' in status:
+ s = '{!info!}Label: {!input!}%s' % status['label']
+ self.console.write(s)
if detailed:
- self.console.write('{!info!}Files in torrent')
+ self.console.write('\n{!info!}Files in torrent')
self.show_file_info(torrent_id, status)
self.console.write('{!info!}Connected peers')
self.show_peer_info(torrent_id, status)
@@ -433,7 +437,7 @@ class Command(BaseCommand):
up_color = colors.state_color['Seeding']
down_color = colors.state_color['Downloading']
- s = '%s%s' % (
+ s = '{}{}'.format(
colors.state_color[status['state']],
'[' + status['state'][0] + ']',
)
@@ -458,7 +462,7 @@ class Command(BaseCommand):
)
if status['download_payload_rate'] > 0:
- dl_info += ' @ %s%s' % (
+ dl_info += ' @ {}{}'.format(
down_color,
fspeed(status['download_payload_rate'], shortform=True),
)
@@ -468,7 +472,7 @@ class Command(BaseCommand):
status['total_uploaded'], status['total_payload_upload']
)
if status['upload_payload_rate'] > 0:
- ul_info += ' @ %s%s' % (
+ ul_info += ' @ {}{}'.format(
up_color,
fspeed(status['upload_payload_rate'], shortform=True),
)
diff --git a/deluge/ui/console/cmdline/commands/manage.py b/deluge/ui/console/cmdline/commands/manage.py
index 6375a74c3..e5ea9b255 100644
--- a/deluge/ui/console/cmdline/commands/manage.py
+++ b/deluge/ui/console/cmdline/commands/manage.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from twisted.internet import defer
@@ -69,7 +66,7 @@ class Command(BaseCommand):
self.console.write('{!info!}ID: {!input!}%s' % torrentid)
for k, v in data.items():
if k != 'name':
- self.console.write('{!info!}%s: {!input!}%s' % (k, v))
+ self.console.write(f'{{!info!}}{k}: {{!input!}}{v}')
def on_torrents_status_fail(reason):
self.console.write('{!error!}Failed to get torrent data.')
@@ -106,9 +103,7 @@ class Command(BaseCommand):
self.console.write('{!success!}Torrent option successfully updated.')
deferred.callback(True)
- self.console.write(
- 'Setting %s to %s for torrents %s..' % (key, val, torrent_ids)
- )
+ self.console.write(f'Setting {key} to {val} for torrents {torrent_ids}..')
client.core.set_torrent_options(torrent_ids, {key: val}).addCallback(
on_set_config
)
diff --git a/deluge/ui/console/cmdline/commands/move.py b/deluge/ui/console/cmdline/commands/move.py
index 13e475e6f..67ee0af1d 100644
--- a/deluge/ui/console/cmdline/commands/move.py
+++ b/deluge/ui/console/cmdline/commands/move.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os.path
@@ -52,7 +49,7 @@ class Command(BaseCommand):
names.append(self.console.get_torrent_name(tid))
def on_move(res):
- msg = 'Moved "%s" to %s' % (', '.join(names), options.path)
+ msg = 'Moved "{}" to {}'.format(', '.join(names), options.path)
self.console.write(msg)
log.info(msg)
diff --git a/deluge/ui/console/cmdline/commands/pause.py b/deluge/ui/console/cmdline/commands/pause.py
index 1f7ef31a0..133424267 100644
--- a/deluge/ui/console/cmdline/commands/pause.py
+++ b/deluge/ui/console/cmdline/commands/pause.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from deluge.ui.client import client
diff --git a/deluge/ui/console/cmdline/commands/plugin.py b/deluge/ui/console/cmdline/commands/plugin.py
index 72cecb40f..c424cb201 100644
--- a/deluge/ui/console/cmdline/commands/plugin.py
+++ b/deluge/ui/console/cmdline/commands/plugin.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
import deluge.configmanager
from deluge.ui.client import client
diff --git a/deluge/ui/console/cmdline/commands/quit.py b/deluge/ui/console/cmdline/commands/quit.py
index 261a01a9b..4459dfc70 100644
--- a/deluge/ui/console/cmdline/commands/quit.py
+++ b/deluge/ui/console/cmdline/commands/quit.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from . import BaseCommand
diff --git a/deluge/ui/console/cmdline/commands/recheck.py b/deluge/ui/console/cmdline/commands/recheck.py
index c9b6360c9..046cb0b1e 100644
--- a/deluge/ui/console/cmdline/commands/recheck.py
+++ b/deluge/ui/console/cmdline/commands/recheck.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from deluge.ui.client import client
diff --git a/deluge/ui/console/cmdline/commands/resume.py b/deluge/ui/console/cmdline/commands/resume.py
index 1f62c5f00..27b852894 100644
--- a/deluge/ui/console/cmdline/commands/resume.py
+++ b/deluge/ui/console/cmdline/commands/resume.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from deluge.ui.client import client
diff --git a/deluge/ui/console/cmdline/commands/rm.py b/deluge/ui/console/cmdline/commands/rm.py
index c34148ac9..4a3fd008a 100644
--- a/deluge/ui/console/cmdline/commands/rm.py
+++ b/deluge/ui/console/cmdline/commands/rm.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
@@ -74,7 +71,7 @@ class Command(BaseCommand):
'Error(s) occurred when trying to delete torrent(s).'
)
for t_id, e_msg in errors:
- self.console.write('Error removing torrent %s : %s' % (t_id, e_msg))
+ self.console.write(f'Error removing torrent {t_id} : {e_msg}')
log.info('Removing %d torrents', len(torrent_ids))
d = client.core.remove_torrents(torrent_ids, options.remove_data)
diff --git a/deluge/ui/console/cmdline/commands/status.py b/deluge/ui/console/cmdline/commands/status.py
index 948ad6b94..05c9796ce 100644
--- a/deluge/ui/console/cmdline/commands/status.py
+++ b/deluge/ui/console/cmdline/commands/status.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from twisted.internet import defer
@@ -65,7 +62,12 @@ class Command(BaseCommand):
deferreds = []
ds = client.core.get_session_status(
- ['num_peers', 'payload_upload_rate', 'payload_download_rate', 'dht_nodes']
+ [
+ 'peer.num_peers_connected',
+ 'payload_upload_rate',
+ 'payload_download_rate',
+ 'dht.dht_nodes',
+ ]
)
ds.addCallback(on_session_status)
deferreds.append(ds)
@@ -95,7 +97,7 @@ class Command(BaseCommand):
'{!info!}Total download: %s'
% fspeed(self.status['payload_download_rate'])
)
- self.console.write('{!info!}DHT Nodes: %i' % self.status['dht_nodes'])
+ self.console.write('{!info!}DHT Nodes: %i' % self.status['dht.dht_nodes'])
if isinstance(self.torrents, int):
if self.torrents == -2:
diff --git a/deluge/ui/console/cmdline/commands/update_tracker.py b/deluge/ui/console/cmdline/commands/update_tracker.py
index 591b95192..c05569d7b 100644
--- a/deluge/ui/console/cmdline/commands/update_tracker.py
+++ b/deluge/ui/console/cmdline/commands/update_tracker.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.component as component
from deluge.ui.client import client
diff --git a/deluge/ui/console/console.py b/deluge/ui/console/console.py
index f683c749d..8ef87e8de 100644
--- a/deluge/ui/console/console.py
+++ b/deluge/ui/console/console.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -7,8 +6,6 @@
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import fnmatch
import logging
import os
@@ -53,7 +50,7 @@ def load_commands(command_dir):
return dict(commands)
-class LogStream(object):
+class LogStream:
out = sys.stdout
def write(self, data):
@@ -68,9 +65,7 @@ class Console(UI):
cmd_description = """Console or command-line user interface"""
def __init__(self, *args, **kwargs):
- super(Console, self).__init__(
- 'console', *args, log_stream=LogStream(), **kwargs
- )
+ super().__init__('console', *args, log_stream=LogStream(), **kwargs)
group = self.parser.add_argument_group(
_('Console Options'),
@@ -150,7 +145,7 @@ class Console(UI):
self.console_parser.subcommand = False
self.parser.subcommand = False if i == -1 else True
- super(Console, self).start(self.console_parser)
+ super().start(self.console_parser)
from deluge.ui.console.main import ConsoleUI # import here because (see top)
def run(options):
diff --git a/deluge/ui/console/main.py b/deluge/ui/console/main.py
index c74d9022f..31d1db177 100644
--- a/deluge/ui/console/main.py
+++ b/deluge/ui/console/main.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import locale
import logging
import os
@@ -67,7 +64,7 @@ DEFAULT_CONSOLE_PREFS = {
}
-class MockConsoleLog(object):
+class MockConsoleLog:
def write(self, data):
pass
@@ -286,7 +283,7 @@ deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent"
@overrides(TermResizeHandler)
def on_terminal_size(self, *args):
- rows, cols = super(ConsoleUI, self).on_terminal_size(args)
+ rows, cols = super().on_terminal_size(args)
for mode in self.modes:
self.modes[mode].on_resize(rows, cols)
@@ -711,7 +708,7 @@ class EventLog(component.Component):
if not t_name:
return
- self.write('%s: {!info!}%s ({!cyan!}%s{!info!})' % (state, t_name, torrent_id))
+ self.write(f'{state}: {{!info!}}{t_name} ({{!cyan!}}{torrent_id}{{!info!}})')
def on_torrent_finished_event(self, torrent_id):
if component.get('TorrentList').config['ring_bell']:
@@ -739,7 +736,7 @@ class EventLog(component.Component):
except KeyError:
pass
- self.write('ConfigValueChanged: {!input!}%s: %s%s' % (key, color, value))
+ self.write(f'ConfigValueChanged: {{!input!}}{key}: {color}{value}')
def write(self, s):
current_time = time.localtime()
@@ -753,8 +750,6 @@ class EventLog(component.Component):
if date_different:
string = time.strftime(self.date_change_format)
- if deluge.common.PY2:
- string = string.decode()
self.console.write_event(' ')
self.console.write_event(string)
diff --git a/deluge/ui/console/modes/add_util.py b/deluge/ui/console/modes/add_util.py
index ac60b8974..9d29a1f4f 100644
--- a/deluge/ui/console/modes/add_util.py
+++ b/deluge/ui/console/modes/add_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -9,15 +8,11 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import glob
import logging
import os
from base64 import b64encode
-from six import unichr as chr # noqa: A001 shadowing
-
import deluge.common
from deluge.ui.client import client
from deluge.ui.common import TorrentInfo
diff --git a/deluge/ui/console/modes/addtorrents.py b/deluge/ui/console/modes/addtorrents.py
index 6b2c105d9..217b63d85 100644
--- a/deluge/ui/console/modes/addtorrents.py
+++ b/deluge/ui/console/modes/addtorrents.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 Arek Stefański <asmageddon@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
from base64 import b64encode
@@ -24,12 +21,6 @@ from deluge.ui.console.utils import format_utils
from deluge.ui.console.widgets.popup import InputPopup, MessagePopup
try:
- from future_builtins import zip
-except ImportError:
- # Ignore on Py3.
- pass
-
-try:
import curses
except ImportError:
pass
@@ -377,7 +368,7 @@ class AddTorrents(BaseMode):
def fail_cb(msg, t_file, ress):
log.debug('failed to add torrent: %s: %s', t_file, msg)
ress['fail'] += 1
- ress['fmsg'].append('{!input!} * %s: {!error!}%s' % (t_file, msg))
+ ress['fmsg'].append(f'{{!input!}} * {t_file}: {{!error!}}{msg}')
if (ress['succ'] + ress['fail']) >= ress['total']:
report_add_status(
component.get('TorrentList'),
@@ -526,9 +517,9 @@ class AddTorrents(BaseMode):
self.last_mark = self.cursel
elif chr(c) == 'j':
- self.scroll_list_up(1)
- elif chr(c) == 'k':
self.scroll_list_down(1)
+ elif chr(c) == 'k':
+ self.scroll_list_up(1)
elif chr(c) == 'M':
if self.last_mark != -1:
if self.last_mark > self.cursel:
diff --git a/deluge/ui/console/modes/basemode.py b/deluge/ui/console/modes/basemode.py
index 5267eae5a..5ebaf86fe 100644
--- a/deluge/ui/console/modes/basemode.py
+++ b/deluge/ui/console/modes/basemode.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import sys
@@ -36,7 +33,7 @@ except ImportError:
log = logging.getLogger(__name__)
-class InputKeyHandler(object):
+class InputKeyHandler:
def __init__(self):
self._input_result = None
@@ -62,7 +59,7 @@ class InputKeyHandler(object):
return util.ReadState.IGNORED
-class TermResizeHandler(object):
+class TermResizeHandler:
def __init__(self):
try:
signal.signal(signal.SIGWINCH, self.on_terminal_size)
@@ -80,14 +77,14 @@ class TermResizeHandler(object):
return rows, cols
-class CursesStdIO(object):
+class CursesStdIO:
"""
fake fd to be registered as a reader with the twisted reactor.
Curses classes needing input should extend this
"""
def fileno(self):
- """ We want to select on FD 0 """
+ """We want to select on FD 0"""
return 0
def doRead(self): # NOQA: N802
diff --git a/deluge/ui/console/modes/cmdline.py b/deluge/ui/console/modes/cmdline.py
index 2735168db..7b0ff2dfc 100644
--- a/deluge/ui/console/modes/cmdline.py
+++ b/deluge/ui/console/modes/cmdline.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -8,16 +7,12 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
import re
-from io import open
import deluge.component as component
import deluge.configmanager
-from deluge.common import PY2
from deluge.decorators import overrides
from deluge.ui.console.cmdline.command import Commander
from deluge.ui.console.modes.basemode import BaseMode, move_cursor
@@ -139,18 +134,18 @@ class CmdLine(BaseMode, Commander):
self._hf_lines = [0, 0]
if self.console_config['cmdline']['save_command_history']:
try:
- with open(self.history_file[0], 'r', encoding='utf8') as _file:
+ with open(self.history_file[0], encoding='utf8') as _file:
lines1 = _file.read().splitlines()
self._hf_lines[0] = len(lines1)
- except IOError:
+ except OSError:
lines1 = []
self._hf_lines[0] = 0
try:
- with open(self.history_file[1], 'r', encoding='utf8') as _file:
+ with open(self.history_file[1], encoding='utf8') as _file:
lines2 = _file.read().splitlines()
self._hf_lines[1] = len(lines2)
- except IOError:
+ except OSError:
lines2 = []
self._hf_lines[1] = 0
@@ -332,10 +327,10 @@ class CmdLine(BaseMode, Commander):
# A key to add to the input string
else:
- if c > 31 and c < 256:
+ if 31 < c < 256:
# Emulate getwch
stroke = chr(c)
- uchar = '' if PY2 else stroke
+ uchar = stroke
while not uchar:
try:
uchar = stroke.decode(self.encoding)
@@ -826,21 +821,21 @@ class CmdLine(BaseMode, Commander):
# Let's avoid listing all torrents twice if there's no pattern
if not empty and torrent_id.startswith(line):
# Highlight the matching part
- text = '{!info!}%s{!input!}%s - "%s"' % (
+ text = '{{!info!}}{}{{!input!}}{} - "{}"'.format(
torrent_id[:line_len],
torrent_id[line_len:],
torrent_name,
)
possible_matches.append(text)
if torrent_name.startswith(line):
- text = '{!info!}%s{!input!}%s ({!cyan!}%s{!input!})' % (
+ text = '{{!info!}}{}{{!input!}}{} ({{!cyan!}}{}{{!input!}})'.format(
escaped_name[:line_len],
escaped_name[line_len:],
torrent_id,
)
possible_matches.append(text)
elif torrent_name.lower().startswith(line.lower()):
- text = '{!info!}%s{!input!}%s ({!cyan!}%s{!input!})' % (
+ text = '{{!info!}}{}{{!input!}}{} ({{!cyan!}}{}{{!input!}})'.format(
escaped_name[:line_len],
escaped_name[line_len:],
torrent_id,
diff --git a/deluge/ui/console/modes/connectionmanager.py b/deluge/ui/console/modes/connectionmanager.py
index a5c596860..0ccdd93db 100644
--- a/deluge/ui/console/modes/connectionmanager.py
+++ b/deluge/ui/console/modes/connectionmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
@@ -33,9 +30,12 @@ class ConnectionManager(BaseMode, PopupsHandler):
self.all_torrents = None
self.hostlist = HostList()
BaseMode.__init__(self, stdscr, encoding=encoding)
- self.update_hosts_status()
def update_select_host_popup(self):
+ if self.popup and not isinstance(self.popup, SelectablePopup):
+ # Ignore MessagePopup on popup stack upon connect fail
+ return
+
selected_index = self.popup.current_selection() if self.popup else None
popup = SelectablePopup(
@@ -50,22 +50,24 @@ class ConnectionManager(BaseMode, PopupsHandler):
% (_('Quit'), _('Add Host'), _('Delete Host')),
space_below=True,
)
- self.push_popup(popup, clear=True)
for host_entry in self.hostlist.get_hosts_info():
host_id, hostname, port, user = host_entry
- args = {'data': host_id, 'foreground': 'red'}
- state = 'Offline'
- if host_id in self.statuses:
- state = 'Online'
- args.update({'data': self.statuses[host_id], 'foreground': 'green'})
- host_str = '%s:%d [%s]' % (hostname, port, state)
- self.popup.add_line(
+ host_status = self.statuses.get(host_id)
+
+ state = host_status[1] if host_status else 'Offline'
+ state_color = 'green' if state in ('Online', 'Connected') else 'red'
+ host_str = f'{hostname}:{port} [{state}]'
+
+ args = {'data': host_id, 'foreground': state_color}
+ popup.add_line(
host_id, host_str, selectable=True, use_underline=True, **args
)
if selected_index:
- self.popup.set_selection(selected_index)
+ popup.set_selection(selected_index)
+
+