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)
+
+ self.push_popup(popup, clear=True)
self.inlist = True
self.refresh()
@@ -85,7 +87,7 @@ class ConnectionManager(BaseMode, PopupsHandler):
d.addCallback(on_console_start)
def _on_connect_fail(self, result):
- self.report_message('Failed to connect!', result)
+ self.report_message('Failed to connect!', result.getErrorMessage())
self.refresh()
if hasattr(result, 'getTraceback'):
log.exception(result)
@@ -128,7 +130,7 @@ class ConnectionManager(BaseMode, PopupsHandler):
try:
self.hostlist.add_host(hostname, port, username, password)
except ValueError as ex:
- self.report_message(_('Error adding host'), '%s: %s' % (hostname, ex))
+ self.report_message(_('Error adding host'), f'{hostname}: {ex}')
else:
self.update_select_host_popup()
@@ -167,7 +169,9 @@ class ConnectionManager(BaseMode, PopupsHandler):
if not self.popup:
self.update_select_host_popup()
- self.popup.refresh()
+ if self.popup:
+ self.popup.refresh()
+
curses.doupdate()
@overrides(BaseMode)
diff --git a/deluge/ui/console/modes/eventview.py b/deluge/ui/console/modes/eventview.py
index cd3308cf9..b6e63b019 100644
--- a/deluge/ui/console/modes/eventview.py
+++ b/deluge/ui/console/modes/eventview.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
@@ -100,9 +97,9 @@ class EventView(BaseMode):
elif c == curses.KEY_END:
self.offset += num_events
elif c == ord('j'):
- self.offset -= 1
- elif c == ord('k'):
self.offset += 1
+ elif c == ord('k'):
+ self.offset -= 1
if self.offset <= 0:
self.offset = 0
diff --git a/deluge/ui/console/modes/preferences/__init__.py b/deluge/ui/console/modes/preferences/__init__.py
index 15d77c4a8..e827d91a3 100644
--- a/deluge/ui/console/modes/preferences/__init__.py
+++ b/deluge/ui/console/modes/preferences/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
from deluge.ui.console.modes.preferences.preferences import Preferences
__all__ = ['Preferences']
diff --git a/deluge/ui/console/modes/preferences/preference_panes.py b/deluge/ui/console/modes/preferences/preference_panes.py
index 8663d8a9c..b47bc4b07 100644
--- a/deluge/ui/console/modes/preferences/preference_panes.py
+++ b/deluge/ui/console/modes/preferences/preference_panes.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
@@ -7,11 +6,9 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
-from deluge.common import is_ip
+from deluge.common import is_interface
from deluge.decorators import overrides
from deluge.i18n import get_languages
from deluge.ui.client import client
@@ -94,11 +91,12 @@ class BasePreferencePane(BaseInputPane, BaseWindow, PopupsHandler):
)
elif ipt.name == 'listen_interface':
listen_interface = ipt.get_value().strip()
- if is_ip(listen_interface) or not listen_interface:
+ if is_interface(listen_interface) or not listen_interface:
conf_dict['listen_interface'] = listen_interface
elif ipt.name == 'outgoing_interface':
outgoing_interface = ipt.get_value().strip()
- conf_dict['outgoing_interface'] = outgoing_interface
+ if is_interface(outgoing_interface) or not outgoing_interface:
+ conf_dict['outgoing_interface'] = outgoing_interface
elif ipt.name.startswith('proxy_'):
if ipt.name == 'proxy_type':
conf_dict.setdefault('proxy', {})['type'] = ipt.get_value()
diff --git a/deluge/ui/console/modes/preferences/preferences.py b/deluge/ui/console/modes/preferences/preferences.py
index 45a39a621..2c95323c6 100644
--- a/deluge/ui/console/modes/preferences/preferences.py
+++ b/deluge/ui/console/modes/preferences/preferences.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 collections import deque
@@ -74,7 +71,7 @@ arrow to edit the other value, and escape to get back to the check box.
"""
-class ZONE(object):
+class ZONE:
length = 3
CATEGORIES, PREFRENCES, ACTIONS = list(range(length))
diff --git a/deluge/ui/console/modes/torrentdetail.py b/deluge/ui/console/modes/torrentdetail.py
index 758cac878..16bd08a5c 100644
--- a/deluge/ui/console/modes/torrentdetail.py
+++ b/deluge/ui/console/modes/torrentdetail.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 division, unicode_literals
-
import logging
import deluge.component as component
@@ -425,9 +422,9 @@ class TorrentDetail(BaseMode, PopupsHandler):
attr = 'bold'
if attr:
- color_string = '{!%s,%s,%s!}' % (fg, bg, attr)
+ color_string = f'{{!{fg},{bg},{attr}!}}'
else:
- color_string = '{!%s,%s!}' % (fg, bg)
+ color_string = f'{{!{fg},{bg}!}}'
# actually draw the dir/file string
if fl[3] and fl[4]: # this is an expanded directory
@@ -439,7 +436,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
r = format_row(
[
- '%s%s %s' % (' ' * depth, xchar, fl[0]),
+ '{}{} {}'.format(' ' * depth, xchar, fl[0]),
fsize(fl[2]),
fl[5],
format_priority(fl[6]),
@@ -447,7 +444,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
self.column_widths,
)
- self.add_string(off, '%s%s' % (color_string, r), trim=False)
+ self.add_string(off, f'{color_string}{r}', trim=False)
off += 1
if fl[3] and fl[4]:
@@ -502,7 +499,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
download_color = colors.state_color['Downloading']
def add_field(name, row, pre_color='{!info!}', post_color='{!input!}'):
- s = '%s%s: %s%s' % (
+ s = '{}{}: {}{}'.format(
pre_color,
torrent_data_fields[name]['name'],
post_color,
@@ -523,7 +520,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
if status['progress'] != 100.0:
s += '/%s' % fsize(status['total_wanted'])
if status['download_payload_rate'] > 0:
- s += ' {!yellow!}@ %s%s' % (
+ s += ' {{!yellow!}}@ {}{}'.format(
download_color,
fsize(status['download_payload_rate']),
)
@@ -534,7 +531,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
# Print UL info and ratio
s = add_field('uploaded', 0, download_color)
if status['upload_payload_rate'] > 0:
- s += ' {!yellow!}@ %s%s' % (
+ s += ' {{!yellow!}}@ {}{}'.format(
colors.state_color['Seeding'],
fsize(status['upload_payload_rate']),
)
@@ -542,13 +539,13 @@ class TorrentDetail(BaseMode, PopupsHandler):
row = self.add_string(row, s)
# Seed/peer info
- s = '{!info!}%s:{!green!} %s {!input!}(%s)' % (
+ s = '{{!info!}}{}:{{!green!}} {} {{!input!}}({})'.format(
torrent_data_fields['seeds']['name'],
status['num_seeds'],
status['total_seeds'],
)
row = self.add_string(row, s)
- s = '{!info!}%s:{!red!} %s {!input!}(%s)' % (
+ s = '{{!info!}}{}:{{!red!}} {} {{!input!}}({})'.format(
torrent_data_fields['peers']['name'],
status['num_peers'],
status['total_peers'],
@@ -557,7 +554,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
# Tracker
tracker_color = '{!green!}' if status['message'] == 'OK' else '{!red!}'
- s = '{!info!}%s: {!magenta!}%s{!input!} says "%s%s{!input!}"' % (
+ s = '{{!info!}}{}: {{!magenta!}}{}{{!input!}} says "{}{}{{!input!}}"'.format(
torrent_data_fields['tracker']['name'],
status['tracker_host'],
tracker_color,
@@ -566,13 +563,13 @@ class TorrentDetail(BaseMode, PopupsHandler):
row = self.add_string(row, s)
# Pieces and availability
- s = '{!info!}%s: {!yellow!}%s {!input!}x {!yellow!}%s' % (
+ s = '{{!info!}}{}: {{!yellow!}}{} {{!input!}}x {{!yellow!}}{}'.format(
torrent_data_fields['pieces']['name'],
status['num_pieces'],
fsize(status['piece_length']),
)
if status['distributed_copies']:
- s += '{!info!}%s: {!input!}%s' % (
+ s += '{{!info!}}{}: {{!input!}}{}'.format(
torrent_data_fields['seed_rank']['name'],
status['seed_rank'],
)
@@ -878,7 +875,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
idx += 1
continue
if num == idx:
- return '%s%s/' % (path, element[0])
+ return f'{path}{element[0]}/'
if element[4]:
i = self._get_full_folder_path(
num, element[3], path + element[0] + '/', idx + 1
@@ -923,7 +920,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
self.popup.close(None, call_cb=False)
return
old_fname = self._get_full_folder_path(self.current_file_idx)
- new_fname = '%s/%s/' % (
+ new_fname = '{}/{}/'.format(
old_fname.strip('/').rpartition('/')[0],
result['new_foldername']['value'],
)
@@ -949,7 +946,7 @@ class TorrentDetail(BaseMode, PopupsHandler):
):
self.popup.close(None, call_cb=False)
return
- fname = '%s/%s' % (
+ fname = '{}/{}'.format(
self.full_names[idx].rpartition('/')[0],
result['new_filename']['value'],
)
@@ -1019,8 +1016,8 @@ class TorrentDetail(BaseMode, PopupsHandler):
elif c == ord('h'):
self.push_popup(MessagePopup(self, 'Help', HELP_STR, width_req=0.75))
elif c == ord('j'):
- self.file_list_up()
- elif c == ord('k'):
self.file_list_down()
+ elif c == ord('k'):
+ self.file_list_up()
self.refresh()
diff --git a/deluge/ui/console/modes/torrentlist/__init__.py b/deluge/ui/console/modes/torrentlist/__init__.py
index 18c4db377..48c60ce5a 100644
--- a/deluge/ui/console/modes/torrentlist/__init__.py
+++ b/deluge/ui/console/modes/torrentlist/__init__.py
@@ -1,7 +1,4 @@
-from __future__ import unicode_literals
-
-
-class ACTION(object):
+class ACTION:
PAUSE = 'pause'
RESUME = 'resume'
REANNOUNCE = 'update_tracker'
diff --git a/deluge/ui/console/modes/torrentlist/add_torrents_popup.py b/deluge/ui/console/modes/torrentlist/add_torrents_popup.py
index b0ac483a0..3ff9ab78d 100644
--- a/deluge/ui/console/modes/torrentlist/add_torrents_popup.py
+++ b/deluge/ui/console/modes/torrentlist/add_torrents_popup.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.common
@@ -40,7 +37,7 @@ def show_torrent_add_popup(torrentlist):
def fail_cb(msg, url):
log.debug('failed to add torrent: %s: %s', url, msg)
- error_msg = '{!input!} * %s: {!error!}%s' % (url, msg)
+ error_msg = f'{{!input!}} * {url}: {{!error!}}{msg}'
report_add_status(torrentlist, 0, 1, [error_msg])
def success_cb(tid, url):
diff --git a/deluge/ui/console/modes/torrentlist/filtersidebar.py b/deluge/ui/console/modes/torrentlist/filtersidebar.py
index 0f39b5c3c..982e2457a 100644
--- a/deluge/ui/console/modes/torrentlist/filtersidebar.py
+++ b/deluge/ui/console/modes/torrentlist/filtersidebar.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import curses
import logging
diff --git a/deluge/ui/console/modes/torrentlist/queue_mode.py b/deluge/ui/console/modes/torrentlist/queue_mode.py
index 0c44aafdf..33af0135d 100644
--- a/deluge/ui/console/modes/torrentlist/queue_mode.py
+++ b/deluge/ui/console/modes/torrentlist/queue_mode.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
-
from deluge.ui.client import client
from deluge.ui.console.utils import curses_util as util
from deluge.ui.console.widgets.popup import MessagePopup, SelectablePopup
@@ -38,7 +35,7 @@ Change queue position of selected torrents
"""
-class QueueMode(object):
+class QueueMode:
def __init__(self, torrentslist, torrent_ids):
self.torrentslist = torrentslist
self.torrentview = torrentslist.torrentview
diff --git a/deluge/ui/console/modes/torrentlist/search_mode.py b/deluge/ui/console/modes/torrentlist/search_mode.py
index 57a8e5f64..6f79628fb 100644
--- a/deluge/ui/console/modes/torrentlist/search_mode.py
+++ b/deluge/ui/console/modes/torrentlist/search_mode.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
@@ -7,11 +6,8 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
-from deluge.common import PY2
from deluge.decorators import overrides
from deluge.ui.console.modes.basemode import InputKeyHandler, move_cursor
from deluge.ui.console.modes.torrentlist.torrentactions import torrent_actions_popup
@@ -49,7 +45,7 @@ SEARCH_FORMAT = {
class SearchMode(InputKeyHandler):
def __init__(self, torrentlist):
- super(SearchMode, self).__init__()
+ super().__init__()
self.torrentlist = torrentlist
self.torrentview = torrentlist.torrentview
self.search_state = SEARCH_EMPTY
@@ -176,7 +172,7 @@ class SearchMode(InputKeyHandler):
elif c > 31 and c < 256:
old_search_string = self.search_string
stroke = chr(c)
- uchar = '' if PY2 else stroke
+ uchar = stroke
while not uchar:
try:
uchar = stroke.decode(self.torrentlist.encoding)
diff --git a/deluge/ui/console/modes/torrentlist/torrentactions.py b/deluge/ui/console/modes/torrentlist/torrentactions.py
index f3cd39509..6450118c6 100644
--- a/deluge/ui/console/modes/torrentlist/torrentactions.py
+++ b/deluge/ui/console/modes/torrentlist/torrentactions.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
@@ -46,7 +43,7 @@ def action_remove(mode=None, torrent_ids=None, **kwargs):
if errors:
error_msgs = ''
for t_id, e_msg in errors:
- error_msgs += 'Error removing torrent %s : %s\n' % (t_id, e_msg)
+ error_msgs += f'Error removing torrent {t_id} : {e_msg}\n'
mode.report_message(
'Error(s) occured when trying to delete torrent(s).', error_msgs
)
@@ -77,7 +74,7 @@ def action_remove(mode=None, torrent_ids=None, **kwargs):
show_max = 6
for i, (name, state) in enumerate(status):
color = colors.state_color[state]
- rem_msg += '\n %s* {!input!}%s' % (color, name)
+ rem_msg += f'\n {color}* {{!input!}}{name}'
if i == show_max - 1:
if i < len(status) - 1:
rem_msg += '\n {!red!}And %i more' % (len(status) - show_max)
diff --git a/deluge/ui/console/modes/torrentlist/torrentlist.py b/deluge/ui/console/modes/torrentlist/torrentlist.py
index a427d65b0..d3c32ec0e 100644
--- a/deluge/ui/console/modes/torrentlist/torrentlist.py
+++ b/deluge/ui/console/modes/torrentlist/torrentlist.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 collections import deque
@@ -179,7 +176,7 @@ class TorrentList(BaseMode, PopupsHandler):
@overrides(BaseMode)
def resume(self):
- super(TorrentList, self).resume()
+ super().resume()
@overrides(BaseMode)
def on_resize(self, rows, cols):
@@ -222,7 +219,9 @@ class TorrentList(BaseMode, PopupsHandler):
# Update the status bars
statusbar_args = {'scr': self.stdscr, 'bottombar_help': True}
if self.torrentview.curr_filter is not None:
- statusbar_args['topbar'] = '%s {!filterstatus!}Current filter: %s' % (
+ statusbar_args[
+ 'topbar'
+ ] = '{} {{!filterstatus!}}Current filter: {}'.format(
self.statusbars.topbar,
self.torrentview.curr_filter,
)
diff --git a/deluge/ui/console/modes/torrentlist/torrentview.py b/deluge/ui/console/modes/torrentlist/torrentview.py
index 67de3e786..1ce509788 100644
--- a/deluge/ui/console/modes/torrentlist/torrentview.py
+++ b/deluge/ui/console/modes/torrentlist/torrentview.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 unicode_literals
-
import logging
import deluge.component as component
@@ -90,7 +87,7 @@ for col_i, col_name in enumerate(torrentviewcolumns.column_pref_names):
class TorrentView(InputKeyHandler):
def __init__(self, torrentlist, config):
- super(TorrentView, self).__init__()
+ super().__init__()
self.torrentlist = torrentlist
self.config = config
self.filter_dict = {}
@@ -331,7 +328,7 @@ class TorrentView(InputKeyHandler):
self.torrentlist.add_string(
currow + self.torrentlist_offset,
- '%s%s' % (colorstr, row[0]),
+ f'{colorstr}{row[0]}',
trim=False,
scr=self.torrentlist.torrentview_panel,
)
@@ -467,9 +464,9 @@ class TorrentView(InputKeyHandler):
)
self.torrentlist.refresh()
elif c == ord('j'):
- affected_lines = self._scroll_up(1)
- elif c == ord('k'):
affected_lines = self._scroll_down(1)
+ elif c == ord('k'):
+ affected_lines = self._scroll_up(1)
elif c == ord('m'):
self.mark_unmark(self.cursel)
affected_lines = [self.cursel]
diff --git a/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py b/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py
index 9dff84306..586a56978 100644
--- a/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py
+++ b/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.decorators import overrides
from deluge.ui.console.utils import curses_util as util
from deluge.ui.console.utils.column import torrent_data_fields
diff --git a/deluge/ui/console/parser.py b/deluge/ui/console/parser.py
index 917773aef..c0686b156 100644
--- a/deluge/ui/console/parser.py
+++ b/deluge/ui/console/parser.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import argparse
import shlex
@@ -29,7 +26,7 @@ class ConsoleBaseParser(argparse.ArgumentParser):
# Handle epilog manually to keep the text formatting
epilog = self.epilog
self.epilog = ''
- help_str = super(ConsoleBaseParser, self).format_help()
+ help_str = super().format_help()
if epilog is not None:
help_str += epilog
self.epilog = epilog
@@ -51,7 +48,7 @@ class ConsoleCommandParser(ConsoleBaseParser):
for cmd_line in cmd_lines:
cmds = shlex.split(cmd_line)
- cmd_options = super(ConsoleCommandParser, self).parse_args(args=cmds)
+ cmd_options = super().parse_args(args=cmds)
cmd_options.command = cmds[0]
command_options.append(cmd_options)
@@ -96,7 +93,7 @@ class ConsoleCommandParser(ConsoleBaseParser):
options = self.base_parser.parse_args(args=args)
options.parsed_cmds = []
else:
- options = super(ConsoleCommandParser, self).parse_args(args=args)
+ options = super().parse_args(args=args)
options.parsed_cmds = [options]
if not hasattr(options, 'remaining'):
@@ -107,7 +104,7 @@ class ConsoleCommandParser(ConsoleBaseParser):
class OptionParser(ConsoleBaseParser):
def __init__(self, **kwargs):
- super(OptionParser, self).__init__(**kwargs)
+ super().__init__(**kwargs)
self.formatter = ConsoleColorFormatter()
def exit(self, status=0, msg=None):
@@ -139,5 +136,5 @@ class OptionParser(ConsoleBaseParser):
def format_help(self):
"""Return help formatted with colors."""
- help_str = super(OptionParser, self).format_help()
+ help_str = super().format_help()
return self.formatter.format_colors(help_str)
diff --git a/deluge/ui/console/utils/colors.py b/deluge/ui/console/utils/colors.py
index 587c1f3f6..cc414fea5 100644
--- a/deluge/ui/console/utils/colors.py
+++ b/deluge/ui/console/utils/colors.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 re
@@ -91,8 +88,8 @@ def init_colors():
curses.init_pair(counter, fg, bg)
color_pairs[(fg_name, bg_name)] = counter
counter += 1
- except curses.error as ex:
- log.warning('Error: %s', ex)
+ except (curses.error, ValueError) as ex:
+ log.debug(f'Color pair {fg_name} {bg_name} not available: {ex}')
return counter
# Create the color_pairs dict
@@ -271,7 +268,7 @@ def parse_color_string(string):
last_color_attr = color_pair
attrs = attrs[2:] # Remove colors
except KeyError:
- raise BadColorString('Bad color value in tag: %s,%s' % (fg, bg))
+ raise BadColorString(f'Bad color value in tag: {fg},{bg}')
# Check for additional attributes and OR them to the color_pair
color_pair = apply_attrs(color_pair, attrs)
last_color_attr = color_pair
@@ -292,7 +289,7 @@ def parse_color_string(string):
return ret
-class ConsoleColorFormatter(object):
+class ConsoleColorFormatter:
"""
Format help in a way suited to deluge CmdLine mode - colors, format, indentation...
"""
diff --git a/deluge/ui/console/utils/column.py b/deluge/ui/console/utils/column.py
index d93215957..ecbe04ba3 100644
--- a/deluge/ui/console/utils/column.py
+++ b/deluge/ui/console/utils/column.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 copy
import logging
diff --git a/deluge/ui/console/utils/common.py b/deluge/ui/console/utils/common.py
index df1c07917..fdc88c402 100644
--- a/deluge/ui/console/utils/common.py
+++ b/deluge/ui/console/utils/common.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 unicode_literals
-
TORRENT_OPTIONS = {
'max_download_speed': float,
'max_upload_speed': float,
diff --git a/deluge/ui/console/utils/curses_util.py b/deluge/ui/console/utils/curses_util.py
index a0cd6dc4b..50b044402 100644
--- a/deluge/ui/console/utils/curses_util.py
+++ b/deluge/ui/console/utils/curses_util.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
try:
import curses
except ImportError:
@@ -39,7 +36,7 @@ def is_int_chr(c):
return c > 47 and c < 58
-class Curser(object):
+class Curser:
INVISIBLE = 0
NORMAL = 1
VERY_VISIBLE = 2
@@ -59,7 +56,7 @@ def safe_curs_set(visibility):
pass
-class ReadState(object):
+class ReadState:
IGNORED = 0
READ = 1
CHANGED = 2
diff --git a/deluge/ui/console/utils/format_utils.py b/deluge/ui/console/utils/format_utils.py
index 029fb2011..50ec1915f 100644
--- a/deluge/ui/console/utils/format_utils.py
+++ b/deluge/ui/console/utils/format_utils.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 re
from collections import deque
from unicodedata import east_asian_width, normalize
@@ -98,7 +95,7 @@ def f_seedrank_dash(seed_rank, seeding_time):
def ftotal_sized(first, second):
- return '%s (%s)' % (
+ return '{} ({})'.format(
deluge.common.fsize(first, shortform=True),
deluge.common.fsize(second, shortform=True),
)
@@ -159,7 +156,7 @@ def format_column(col, lim):
if size >= lim - 1:
return trim_string(col, lim, dbls > 0)
else:
- return '%s%s' % (col, ' ' * (lim - size))
+ return '{}{}'.format(col, ' ' * (lim - size))
def format_row(row, column_widths):
@@ -213,7 +210,7 @@ def wrap_string(string, width, min_lines=0, strip_colors=True):
mtc = mtchs.popleft() - offset
clr = clrs.popleft()
end_pos += len(clr)
- s = '%s%s%s' % (s[:mtc], clr, s[mtc:])
+ s = f'{s[:mtc]}{clr}{s[mtc:]}'
return s
for s in s1:
@@ -238,11 +235,11 @@ def wrap_string(string, width, min_lines=0, strip_colors=True):
else:
cstr = s
- def append_indent(l, string, offset):
+ def append_indent(line, string, offset):
"""Prepends indent to string if specified"""
if indent and offset != 0:
string = indent + string
- l.append(string)
+ line.append(string)
while cstr:
# max with for a line. If indent is specified, we account for this
@@ -290,7 +287,7 @@ def wrap_string(string, width, min_lines=0, strip_colors=True):
last_color_string = ''
for i, line in enumerate(ret):
if i != 0:
- ret[i] = '%s%s' % (last_color_string, ret[i])
+ ret[i] = f'{last_color_string}{ret[i]}'
colors = re.findall('\\{![^!]+!\\}', line)
if colors:
@@ -313,9 +310,9 @@ def pad_string(string, length, character=' ', side='right'):
w = strwidth(string)
diff = length - w
if side == 'left':
- return '%s%s' % (character * diff, string)
+ return f'{character * diff}{string}'
elif side == 'right':
- return '%s%s' % (string, character * diff)
+ return f'{string}{character * diff}'
def delete_alt_backspace(input_text, input_cursor, sep_chars=' *?!._~-#$^;\'"/'):
diff --git a/deluge/ui/console/widgets/__init__.py b/deluge/ui/console/widgets/__init__.py
index a11e3f2b8..bc88a3b6b 100644
--- a/deluge/ui/console/widgets/__init__.py
+++ b/deluge/ui/console/widgets/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
from deluge.ui.console.widgets.inputpane import BaseInputPane
from deluge.ui.console.widgets.statusbars import StatusBars
from deluge.ui.console.widgets.window import BaseWindow
diff --git a/deluge/ui/console/widgets/fields.py b/deluge/ui/console/widgets/fields.py
index 021cab738..d8d892d52 100644
--- a/deluge/ui/console/widgets/fields.py
+++ b/deluge/ui/console/widgets/fields.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
@@ -9,12 +8,9 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
-from deluge.common import PY2
from deluge.decorators import overrides
from deluge.ui.console.modes.basemode import InputKeyHandler
from deluge.ui.console.utils import colors
@@ -35,7 +31,7 @@ log = logging.getLogger(__name__)
class BaseField(InputKeyHandler):
def __init__(self, parent=None, name=None, selectable=True, **kwargs):
- super(BaseField, self).__init__()
+ super().__init__()
self.name = name
self.parent = parent
self.fmt_keys = {}
@@ -74,7 +70,7 @@ class BaseField(InputKeyHandler):
def build_fmt_string(self, focused, active, value_key='msg', **kwargs):
color_key, font_key = self.get_fmt_keys(focused, active, **kwargs)
- return '{!%%(%s)s,%%(%s)s!}%%(%s)s{!%%(%s)s!}' % (
+ return '{{!%({})s,%({})s!}}%({})s{{!%({})s!}}'.format(
color_key,
font_key,
value_key,
@@ -176,7 +172,7 @@ class InfoField(NoInputField):
NoInputField.__init__(self, parent=parent, name=name, **kwargs)
self.label = label
self.value = value
- self.txt = '%s %s' % (label, value)
+ self.txt = f'{label} {value}'
@overrides(BaseField)
def render(self, screen, row, col=0, **kwargs):
@@ -187,9 +183,9 @@ class InfoField(NoInputField):
def set_value(self, v):
self.value = v
if isinstance(v, float):
- self.txt = '%s %.2f' % (self.label, self.value)
+ self.txt = f'{self.label} {self.value:.2f}'
else:
- self.txt = '%s %s' % (self.label, self.value)
+ self.txt = f'{self.label} {self.value}'
class CheckedInput(InputField):
@@ -202,7 +198,7 @@ class CheckedInput(InputField):
checked_char='X',
unchecked_char=' ',
checkbox_format='[%s] ',
- **kwargs
+ **kwargs,
):
InputField.__init__(self, parent, name, message, **kwargs)
self.set_value(checked)
@@ -231,9 +227,7 @@ class CheckedInput(InputField):
@overrides(BaseField)
def get_fmt_keys(self, focused, active, **kwargs):
- color_key, font_key = super(CheckedInput, self).get_fmt_keys(
- focused, active, **kwargs
- )
+ color_key, font_key = super().get_fmt_keys(focused, active, **kwargs)
if self.checked:
color_key += '_checked'
font_key += '_checked'
@@ -284,7 +278,7 @@ class CheckedPlusInput(CheckedInput):
child_always_visible=False,
show_usage_hints=True,
msg_fmt='%s ',
- **kwargs
+ **kwargs,
):
CheckedInput.__init__(self, parent, name, message, **kwargs)
self.child = child
@@ -372,7 +366,7 @@ class IntSpinInput(InputField):
incr_large=10,
strict_validation=False,
fmt='%d',
- **kwargs
+ **kwargs,
):
InputField.__init__(self, parent, name, message, **kwargs)
self.convert_func = int
@@ -618,7 +612,7 @@ class SelectInput(InputField):
active_index,
active_default=False,
require_select_action=True,
- **kwargs
+ **kwargs,
):
InputField.__init__(self, parent, name, message, **kwargs)
self.opts = opts
@@ -667,9 +661,7 @@ class SelectInput(InputField):
@overrides(BaseField)
def get_fmt_keys(self, focused, active, selected=False, **kwargs):
- color_key, font_key = super(SelectInput, self).get_fmt_keys(
- focused, active, **kwargs
- )
+ color_key, font_key = super().get_fmt_keys(focused, active, **kwargs)
if selected:
color_key += '_selected'
font_key += '_selected'
@@ -739,7 +731,7 @@ class TextInput(InputField):
value,
complete=False,
activate_input=False,
- **kwargs
+ **kwargs,
):
InputField.__init__(self, parent, name, message, **kwargs)
self.move_func = move_func
@@ -815,7 +807,7 @@ class TextInput(InputField):
focused=True,
col=0,
cursor_offset=0,
- **kwargs
+ **kwargs,
):
if not self.value and not active and len(self.default_value) != 0:
self.value = self.default_value
@@ -951,7 +943,7 @@ class TextInput(InputField):
elif c > 31 and c < 256:
# Emulate getwch
stroke = chr(c)
- uchar = '' if PY2 else stroke
+ uchar = stroke
while not uchar:
try:
uchar = stroke.decode(self.parent.encoding)
@@ -1081,7 +1073,7 @@ class ComboInput(InputField):
choice[1],
selectable=True,
selected=choice[0] == self.get_value(),
- **args
+ **args,
)
self.parent.push_popup(select_popup)
return util.ReadState.CHANGED
@@ -1149,7 +1141,7 @@ class TextArea(TextField):
for i, line in enumerate(lines):
self.parent.add_string(
row + i,
- '%s%s' % (color, line),
+ f'{color}{line}',
scr=screen,
col=col,
pad=False,
@@ -1176,7 +1168,7 @@ class DividerField(NoInputField):
selectable=False,
fill_width=True,
value_fmt='%s',
- **kwargs
+ **kwargs,
):
NoInputField.__init__(
self, parent=parent, name=name, selectable=selectable, **kwargs
diff --git a/deluge/ui/console/widgets/inputpane.py b/deluge/ui/console/widgets/inputpane.py
index 097a6cb8d..d8d217501 100644
--- a/deluge/ui/console/widgets/inputpane.py
+++ b/deluge/ui/console/widgets/inputpane.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
@@ -9,8 +8,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.decorators import overrides
diff --git a/deluge/ui/console/widgets/popup.py b/deluge/ui/console/widgets/popup.py
index d588bbb24..4b0d0274e 100644
--- a/deluge/ui/console/widgets/popup.py
+++ b/deluge/ui/console/widgets/popup.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 deluge.decorators import overrides
@@ -25,7 +22,7 @@ except ImportError:
log = logging.getLogger(__name__)
-class ALIGN(object):
+class ALIGN:
TOP_LEFT = 1
TOP_CENTER = 2
TOP_RIGHT = 3
@@ -38,7 +35,7 @@ class ALIGN(object):
DEFAULT = MIDDLE_CENTER
-class PopupsHandler(object):
+class PopupsHandler:
def __init__(self):
self._popups = []
diff --git a/deluge/ui/console/widgets/sidebar.py b/deluge/ui/console/widgets/sidebar.py
index cc237174d..4015a1375 100644
--- a/deluge/ui/console/widgets/sidebar.py
+++ b/deluge/ui/console/widgets/sidebar.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import curses
import logging
diff --git a/deluge/ui/console/widgets/statusbars.py b/deluge/ui/console/widgets/statusbars.py
index fcf4f2f41..1b9173707 100644
--- a/deluge/ui/console/widgets/statusbars.py
+++ b/deluge/ui/console/widgets/statusbars.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,13 +6,12 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import deluge.common
import deluge.component as component
-from deluge.core.preferencesmanager import DEFAULT_PREFS
from deluge.ui.client import client
+DEFAULT_DAEMON_PORT = 58846
+
class StatusBars(component.Component):
def __init__(self):
@@ -38,19 +36,23 @@ class StatusBars(component.Component):
def on_get_session_status(status):
self.upload = deluge.common.fsize(status['payload_upload_rate'])
self.download = deluge.common.fsize(status['payload_download_rate'])
- self.connections = status['num_peers']
+ self.connections = status['peer.num_peers_connected']
if 'dht_nodes' in status:
- self.dht = status['dht_nodes']
+ self.dht = status['dht.dht_nodes']
self.update_statusbars()
def on_get_external_ip(external_ip):
self.external_ip = external_ip
- keys = ['num_peers', 'payload_upload_rate', 'payload_download_rate']
+ keys = [
+ 'peer.num_peers_connected',
+ 'payload_upload_rate',
+ 'payload_download_rate',
+ ]
if self.config['dht']:
- keys.append('dht_nodes')
+ keys.append('dht.dht_nodes')
client.core.get_session_status(keys).addCallback(on_get_session_status)
client.core.get_external_ip().addCallback(on_get_external_ip)
@@ -76,7 +78,7 @@ class StatusBars(component.Component):
connection_info += '{!white,blue,bold!}@{!red,blue,bold!}%s'
# Port
- if info[1] == DEFAULT_PREFS['daemon_port']:
+ if info[1] == DEFAULT_DAEMON_PORT:
connection_info += '{!white,blue!}:%s'
else:
connection_info += '{!status!}:%s'
diff --git a/deluge/ui/console/widgets/window.py b/deluge/ui/console/widgets/window.py
index 2ef35281e..77aff8817 100644
--- a/deluge/ui/console/widgets/window.py
+++ b/deluge/ui/console/widgets/window.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
@@ -9,8 +8,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from deluge.ui.console.modes.basemode import add_string, mkpad, mkpanel
@@ -24,7 +21,7 @@ except ImportError:
log = logging.getLogger(__name__)
-class BaseWindow(object):
+class BaseWindow:
"""
BaseWindow creates a curses screen to be used for showing panels and popup dialogs
"""
diff --git a/deluge/ui/coreconfig.py b/deluge/ui/coreconfig.py
index ed6b614a2..1e2927b5e 100644
--- a/deluge/ui/coreconfig.py
+++ b/deluge/ui/coreconfig.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 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/ui/countries.py b/deluge/ui/countries.py
index af390853e..eb94df6d9 100644
--- a/deluge/ui/countries.py
+++ b/deluge/ui/countries.py
@@ -1,10 +1,7 @@
-# -*- coding: utf-8 -*-
#
# This file is public domain.
#
-from __future__ import unicode_literals
-
# ISO 3166-1 country names and codes
COUNTRIES = {
'AF': _('Afghanistan'),
diff --git a/deluge/ui/data/share/applications/deluge.desktop.in b/deluge/ui/data/share/applications/deluge.desktop.in
index c952d424a..4335b6da4 100644
--- a/deluge/ui/data/share/applications/deluge.desktop.in
+++ b/deluge/ui/data/share/applications/deluge.desktop.in
@@ -4,6 +4,7 @@ _Name=Deluge
_GenericName=BitTorrent Client
_X-GNOME-FullName=Deluge BitTorrent Client
_Comment=Download and share files over BitTorrent
+_Keywords=bittorrent;torrent;magnet;download;p2p;torrents;downloading;uploading;share;sharing;
TryExec=deluge-gtk
Exec=deluge-gtk %U
Icon=deluge
diff --git a/deluge/ui/gtk3/__init__.py b/deluge/ui/gtk3/__init__.py
index 8e8b19613..8db2773e4 100644
--- a/deluge/ui/gtk3/__init__.py
+++ b/deluge/ui/gtk3/__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
-
import logging
from os import environ
@@ -25,7 +22,7 @@ class Gtk(UI):
cmd_description = """GTK-based graphical user interface"""
def __init__(self, *args, **kwargs):
- super(Gtk, self).__init__(
+ super().__init__(
'gtk', *args, description='Starts the Deluge GTK+ interface', **kwargs
)
@@ -42,7 +39,7 @@ class Gtk(UI):
)
def start(self):
- super(Gtk, self).start()
+ super().start()
import deluge.common
from .gtkui import GtkUI
diff --git a/deluge/ui/gtk3/aboutdialog.py b/deluge/ui/gtk3/aboutdialog.py
index 9974a13de..fe3452b82 100644
--- a/deluge/ui/gtk3/aboutdialog.py
+++ b/deluge/ui/gtk3/aboutdialog.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Marcos Mobley ('markybob') <markybob@gmail.com>
#
@@ -7,7 +6,7 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
+from datetime import date
from gi.repository import Gtk
@@ -18,7 +17,7 @@ from deluge.ui.client import client
from .common import get_deluge_icon, get_pixbuf
-class AboutDialog(object):
+class AboutDialog:
def __init__(self):
self.about = Gtk.AboutDialog()
self.about.set_transient_for(component.get('MainWindow').window)
@@ -38,7 +37,7 @@ class AboutDialog(object):
self.about.set_copyright(
_('Copyright %(year_start)s-%(year_end)s Deluge Team')
- % {'year_start': 2007, 'year_end': 2019}
+ % {'year_start': 2007, 'year_end': date.today().year}
)
self.about.set_comments(
_('A peer-to-peer file sharing program\nutilizing the BitTorrent protocol.')
diff --git a/deluge/ui/gtk3/addtorrentdialog.py b/deluge/ui/gtk3/addtorrentdialog.py
index 81b8dbaf3..cf3851d6c 100644
--- a/deluge/ui/gtk3/addtorrentdialog.py
+++ b/deluge/ui/gtk3/addtorrentdialog.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,11 +6,9 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
import os
-from base64 import b64encode
+from base64 import b64decode, b64encode
from xml.sax.saxutils import escape as xml_escape
from xml.sax.saxutils import unescape as xml_unescape
@@ -19,6 +16,7 @@ from gi.repository import Gtk
from gi.repository.GObject import TYPE_INT64, TYPE_UINT64
import deluge.component as component
+from deluge.bencode import bdecode
from deluge.common import (
create_magnet_uri,
decode_bytes,
@@ -271,6 +269,7 @@ class AddTorrentDialog(component.Component):
return
if metadata:
+ metadata = bdecode(b64decode(metadata))
info = TorrentInfo.from_metadata(metadata, [[t] for t in trackers])
self.files[info_hash] = info.files
self.infos[info_hash] = info.filedata
@@ -775,7 +774,7 @@ class AddTorrentDialog(component.Component):
else:
ErrorDialog(
_('Invalid URL'),
- '%s %s' % (url, _('is not a valid URL.')),
+ '{} {}'.format(url, _('is not a valid URL.')),
self.dialog,
).run()
@@ -817,7 +816,7 @@ class AddTorrentDialog(component.Component):
dialog.destroy()
ErrorDialog(
_('Download Failed'),
- '%s %s' % (_('Failed to download:'), url),
+ '{} {}'.format(_('Failed to download:'), url),
details=result.getErrorMessage(),
parent=self.dialog,
).run()
diff --git a/deluge/ui/gtk3/common.py b/deluge/ui/gtk3/common.py
index e7b46c8d5..42a14b407 100644
--- a/deluge/ui/gtk3/common.py
+++ b/deluge/ui/gtk3/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Marcos Mobley ('markybob') <markybob@gmail.com>
#
@@ -7,15 +6,13 @@
# See LICENSE for more details.
#
"""Common functions for various parts of gtkui to use."""
-from __future__ import unicode_literals
-
import contextlib
import logging
import os
+import pickle
import shutil
import sys
-import six.moves.cPickle as pickle # noqa: N813
from gi.repository.Gdk import SELECTION_CLIPBOARD, SELECTION_PRIMARY, Display
from gi.repository.GdkPixbuf import Colorspace, Pixbuf
from gi.repository.GLib import GError
@@ -29,7 +26,7 @@ from gi.repository.Gtk import (
SortType,
)
-from deluge.common import PY2, get_pixmap, is_ip, osx_check, windows_check
+from deluge.common import get_pixmap, is_ip, osx_check, windows_check
log = logging.getLogger(__name__)
@@ -62,12 +59,36 @@ def create_blank_pixbuf(size=16):
return pix
-def get_pixbuf(filename):
+def get_pixbuf(filename: str, size: int = 0) -> Pixbuf:
+ """Creates a new pixbuf by loading an image from file
+
+ Args:
+ filename: An image file to load
+ size: Specify a size constraint (equal aspect ratio)
+
+ Returns:
+ A newly created pixbuf
+
+ """
+ # Skip ico and gif that cause Pixbuf crash on Windows
+ # https://dev.deluge-torrent.org/ticket/3501
+ if windows_check() and filename.endswith(('.ico', '.gif')):
+ return create_blank_pixbuf(size)
+
+ if not os.path.isabs(filename):
+ filename = get_pixmap(filename)
+
+ pixbuf = None
try:
- return Pixbuf.new_from_file(get_pixmap(filename))
+ if size:
+ pixbuf = Pixbuf.new_from_file_at_size(filename, size, size)
+ else:
+ pixbuf = Pixbuf.new_from_file(filename)
except GError as ex:
+ # Failed to load the pixbuf (Bad image file), so return a blank pixbuf.
log.warning(ex)
- return create_blank_pixbuf()
+
+ return pixbuf or create_blank_pixbuf(size or 16)
# Status icons.. Create them from file only once to avoid constantly re-creating them.
@@ -79,17 +100,6 @@ icon_queued = get_pixbuf('queued16.png')
icon_checking = get_pixbuf('checking16.png')
-def get_pixbuf_at_size(filename, size):
- if not os.path.isabs(filename):
- filename = get_pixmap(filename)
- try:
- return Pixbuf.new_from_file_at_size(filename, size, size)
- except GError as ex:
- # Failed to load the pixbuf (Bad image file), so return a blank pixbuf.
- log.warning(ex)
- return create_blank_pixbuf(size)
-
-
def get_logo(size):
"""A Deluge logo.
@@ -102,7 +112,7 @@ def get_logo(size):
filename = 'deluge.svg'
if windows_check():
filename = 'deluge.png'
- return get_pixbuf_at_size(filename, size)
+ return get_pixbuf(filename, size)
def build_menu_radio_list(
@@ -232,14 +242,11 @@ def associate_magnet_links(overwrite=False):
"""
if windows_check():
- try:
- import winreg
- except ImportError:
- import _winreg as winreg # For Python 2.
+ import winreg
try:
hkey = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'Magnet')
- except WindowsError:
+ except OSError:
overwrite = True
else:
winreg.CloseKey(hkey)
@@ -248,7 +255,7 @@ def associate_magnet_links(overwrite=False):
deluge_exe = os.path.join(os.path.dirname(sys.executable), 'deluge.exe')
try:
magnet_key = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, 'Magnet')
- except WindowsError:
+ except OSError:
# Could not create for all users, falling back to current user
magnet_key = winreg.CreateKey(
winreg.HKEY_CURRENT_USER, 'Software\\Classes\\Magnet'
@@ -257,14 +264,12 @@ def associate_magnet_links(overwrite=False):
winreg.SetValue(magnet_key, '', winreg.REG_SZ, 'URL:Magnet Protocol')
winreg.SetValueEx(magnet_key, 'URL Protocol', 0, winreg.REG_SZ, '')
winreg.SetValueEx(magnet_key, 'BrowserFlags', 0, winreg.REG_DWORD, 0x8)
- winreg.SetValue(
- magnet_key, 'DefaultIcon', winreg.REG_SZ, '{},0'.format(deluge_exe)
- )
+ winreg.SetValue(magnet_key, 'DefaultIcon', winreg.REG_SZ, f'{deluge_exe},0')
winreg.SetValue(
magnet_key,
r'shell\open\command',
winreg.REG_SZ,
- '"{}" "%1"'.format(deluge_exe),
+ f'"{deluge_exe}" "%1"',
)
winreg.CloseKey(magnet_key)
@@ -320,7 +325,7 @@ def save_pickled_state_file(filename, state):
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)
@@ -331,7 +336,7 @@ def save_pickled_state_file(filename, state):
_file.flush()
os.fsync(_file.fileno())
shutil.move(filepath_tmp, filepath)
- except (IOError, EOFError, pickle.PicklingError) as ex:
+ except (OSError, EOFError, pickle.PicklingError) 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)
@@ -356,11 +361,8 @@ def load_pickled_state_file(filename):
log.info('Opening %s for load: %s', filename, _filepath)
try:
with open(_filepath, 'rb') as _file:
- if PY2:
- state = pickle.load(_file)
- else:
- state = pickle.load(_file, encoding='utf8')
- except (IOError, pickle.UnpicklingError) as ex:
+ state = pickle.load(_file, encoding='utf8')
+ except (OSError, pickle.UnpicklingError) as ex:
log.warning('Unable to load %s: %s', _filepath, ex)
else:
log.info('Successfully loaded %s: %s', filename, _filepath)
diff --git a/deluge/ui/gtk3/connectionmanager.py b/deluge/ui/gtk3/connectionmanager.py
index d5883c4b3..b53dd8e04 100644
--- a/deluge/ui/gtk3/connectionmanager.py
+++ b/deluge/ui/gtk3/connectionmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,11 +6,10 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
-from socket import gaierror, gethostbyname
+from socket import gaierror, getaddrinfo
+from urllib.parse import urlparse
from gi.repository import Gtk
from twisted.internet import defer, reactor
@@ -26,12 +24,6 @@ from deluge.ui.hostlist import DEFAULT_PORT, LOCALHOST, HostList
from .common import get_clipboard_text
from .dialogs import AuthenticationDialog, ErrorDialog
-try:
- from urllib.parse import urlparse
-except ImportError:
- # PY2 fallback
- from urlparse import urlparse # pylint: disable=ungrouped-imports
-
log = logging.getLogger(__name__)
HOSTLIST_COL_ID = 0
@@ -230,7 +222,7 @@ class ConnectionManager(component.Component):
__, host, port, __, __, status, __, __ = model[row]
try:
- gethostbyname(host)
+ getaddrinfo(host, None)
except gaierror as ex:
log.error(
'Error resolving host %s to ip: %s', row[HOSTLIST_COL_HOST], ex.args[1]
diff --git a/deluge/ui/gtk3/createtorrentdialog.py b/deluge/ui/gtk3/createtorrentdialog.py
index 1e5e73cb6..e9f16906c 100644
--- a/deluge/ui/gtk3/createtorrentdialog.py
+++ b/deluge/ui/gtk3/createtorrentdialog.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
import os.path
from base64 import b64encode
@@ -31,7 +28,7 @@ from .torrentview_data_funcs import cell_data_size
log = logging.getLogger(__name__)
-class CreateTorrentDialog(object):
+class CreateTorrentDialog:
def __init__(self):
pass
diff --git a/deluge/ui/gtk3/details_tab.py b/deluge/ui/gtk3/details_tab.py
index 2431e0836..04a5eabfe 100644
--- a/deluge/ui/gtk3/details_tab.py
+++ b/deluge/ui/gtk3/details_tab.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from xml.sax.saxutils import escape as xml_escape
@@ -23,7 +20,7 @@ log = logging.getLogger(__name__)
class DetailsTab(Tab):
def __init__(self):
- super(DetailsTab, self).__init__('Details', 'details_tab', 'details_tab_label')
+ super().__init__('Details', 'details_tab', 'details_tab_label')
self.add_tab_widget('summary_name', None, ('name',))
self.add_tab_widget('summary_total_size', fsize, ('total_size',))
@@ -65,7 +62,7 @@ class DetailsTab(Tab):
txt = xml_escape(self.widget_status_as_fstr(widget, status))
if decode_bytes(widget.obj.get_text()) != txt:
if 'comment' in widget.status_keys and is_url(txt):
- widget.obj.set_markup('<a href="%s">%s</a>' % (txt, txt))
+ widget.obj.set_markup(f'<a href="{txt}">{txt}</a>')
else:
widget.obj.set_markup(txt)
diff --git a/deluge/ui/gtk3/dialogs.py b/deluge/ui/gtk3/dialogs.py
index 4a2a60a7e..db337d3d5 100644
--- a/deluge/ui/gtk3/dialogs.py
+++ b/deluge/ui/gtk3/dialogs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -9,7 +8,7 @@
# pylint: disable=super-on-old-class
-from __future__ import unicode_literals
+from collections import namedtuple
from gi.repository import Gtk
from twisted.internet import defer
@@ -17,7 +16,9 @@ from twisted.internet import defer
import deluge.component as component
from deluge.common import windows_check
-from .common import get_deluge_icon, get_pixbuf_at_size
+from .common import get_deluge_icon, get_pixbuf
+
+Account = namedtuple('Account', 'username password authlevel')
class BaseDialog(Gtk.Dialog):
@@ -34,7 +35,7 @@ class BaseDialog(Gtk.Dialog):
:param parent: gtkWindow, the parent window, if None it will default to the
MainWindow
"""
- super(BaseDialog, self).__init__(
+ super().__init__(
title=header,
parent=parent if parent else component.get('MainWindow').window,
flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
@@ -55,7 +56,7 @@ class BaseDialog(Gtk.Dialog):
# Hack for Windows since it doesn't support svg
if icon.endswith('.svg') and windows_check():
icon = icon.rpartition('.svg')[0] + '16.png'
- image.set_from_pixbuf(get_pixbuf_at_size(icon, 24))
+ image.set_from_pixbuf(get_pixbuf(icon, 24))
else:
image.set_from_icon_name(icon, Gtk.IconSize.LARGE_TOOLBAR)
image.set_alignment(0.5, 0.0)
@@ -103,7 +104,7 @@ class YesNoDialog(BaseDialog):
:param text: see `:class:BaseDialog`
:param parent: see `:class:BaseDialog`
"""
- super(YesNoDialog, self).__init__(
+ super().__init__(
header,
text,
'dialog-question',
@@ -127,7 +128,7 @@ class InformationDialog(BaseDialog):
:param text: see `:class:BaseDialog`
:param parent: see `:class:BaseDialog`
"""
- super(InformationDialog, self).__init__(
+ super().__init__(
header,
text,
'dialog-information',
@@ -154,7 +155,7 @@ class ErrorDialog(BaseDialog):
:param traceback: show the traceback information in the details area
:type traceback: bool
"""
- super(ErrorDialog, self).__init__(
+ super().__init__(
header, text, 'dialog-error', (_('_Close'), Gtk.ResponseType.CLOSE), parent
)
@@ -198,7 +199,7 @@ class AuthenticationDialog(BaseDialog):
:param err_msg: the error message we got back from the server
:type err_msg: string
"""
- super(AuthenticationDialog, self).__init__(
+ super().__init__(
_('Authenticate'),
err_msg,
'dialog-password',
@@ -255,7 +256,7 @@ class AccountDialog(BaseDialog):
parent=None,
):
if username:
- super(AccountDialog, self).__init__(
+ super().__init__(
_('Edit Account'),
_('Edit existing account'),
'dialog-information',
@@ -268,7 +269,7 @@ class AccountDialog(BaseDialog):
parent,
)
else:
- super(AccountDialog, self).__init__(
+ super().__init__(
_('New Account'),
_('Create a new account'),
'dialog-information',
@@ -276,21 +277,21 @@ class AccountDialog(BaseDialog):
parent,
)
- self.levels_mapping = levels_mapping
+ self.account = None
table = Gtk.Table(2, 3, False)
- self.username_label = Gtk.Label()
- self.username_label.set_markup('<b>' + _('Username:') + '</b>')
- self.username_label.set_alignment(1.0, 0.5)
- self.username_label.set_padding(5, 5)
+ username_label = Gtk.Label()
+ username_label.set_markup('<b>' + _('Username:') + '</b>')
+ username_label.set_alignment(1.0, 0.5)
+ username_label.set_padding(5, 5)
self.username_entry = Gtk.Entry()
- table.attach(self.username_label, 0, 1, 0, 1)
+ table.attach(username_label, 0, 1, 0, 1)
table.attach(self.username_entry, 1, 2, 0, 1)
- self.authlevel_label = Gtk.Label()
- self.authlevel_label.set_markup('<b>' + _('Authentication Level:') + '</b>')
- self.authlevel_label.set_alignment(1.0, 0.5)
- self.authlevel_label.set_padding(5, 5)
+ authlevel_label = Gtk.Label()
+ authlevel_label.set_markup('<b>' + _('Authentication Level:') + '</b>')
+ authlevel_label.set_alignment(1.0, 0.5)
+ authlevel_label.set_padding(5, 5)
# combo_box_new_text is deprecated but no other pygtk alternative.
self.authlevel_combo = Gtk.ComboBoxText()
@@ -305,16 +306,16 @@ class AccountDialog(BaseDialog):
if active_idx is not None:
self.authlevel_combo.set_active(active_idx)
- table.attach(self.authlevel_label, 0, 1, 1, 2)
+ table.attach(authlevel_label, 0, 1, 1, 2)
table.attach(self.authlevel_combo, 1, 2, 1, 2)
- self.password_label = Gtk.Label()
- self.password_label.set_markup('<b>' + _('Password:') + '</b>')
- self.password_label.set_alignment(1.0, 0.5)
- self.password_label.set_padding(5, 5)
+ password_label = Gtk.Label()
+ password_label.set_markup('<b>' + _('Password:') + '</b>')
+ password_label.set_alignment(1.0, 0.5)
+ password_label.set_padding(5, 5)
self.password_entry = Gtk.Entry()
self.password_entry.set_visibility(False)
- table.attach(self.password_label, 0, 1, 2, 3)
+ table.attach(password_label, 0, 1, 2, 3)
table.attach(self.password_entry, 1, 2, 2, 3)
self.vbox.pack_start(table, False, False, padding=5)
@@ -327,18 +328,17 @@ class AccountDialog(BaseDialog):
if password:
self.password_entry.set_text(username)
- self.show_all()
-
- def get_username(self):
- return self.username_entry.get_text()
-
- def get_password(self):
- return self.password_entry.get_text()
+ self.vbox.show_all()
- def get_authlevel(self):
- combobox = self.authlevel_combo
- level = combobox.get_model()[combobox.get_active()][0]
- return level
+ def _on_response(self, widget, response):
+ if response == Gtk.ResponseType.OK:
+ self.account = Account(
+ self.username_entry.get_text(),
+ self.password_entry.get_text(),
+ self.authlevel_combo.get_active_text(),
+ )
+ self.destroy()
+ self.deferred.callback(response)
class OtherDialog(BaseDialog):
@@ -359,7 +359,7 @@ class OtherDialog(BaseDialog):
if not icon:
icon = 'dialog-information'
- super(OtherDialog, self).__init__(
+ super().__init__(
header,
text,
icon,
@@ -421,7 +421,7 @@ class PasswordDialog(BaseDialog):
:param password_msg: the error message we got back from the server
:type password_msg: string
"""
- super(PasswordDialog, self).__init__(
+ super().__init__(
header=_('Password Protected'),
text=password_msg,
icon='dialog-password',
@@ -455,3 +455,44 @@ class PasswordDialog(BaseDialog):
def on_password_activate(self, widget):
self.response(Gtk.ResponseType.OK)
+
+
+class CopyMagnetDialog(BaseDialog):
+ """
+ Displays a dialog with a magnet URI
+ """
+
+ def __init__(self, torrent_magnet='', parent=None):
+ super().__init__(
+ header=_('Copy Magnet URI'),
+ text='',
+ icon='magnet_copy.svg',
+ buttons=(_('_Close'), Gtk.ResponseType.CLOSE),
+ parent=parent,
+ )
+ self.copied = False
+
+ table = Gtk.Table(1, 2, False)
+ self.magnet_entry = Gtk.Entry()
+ self.magnet_entry.set_text(torrent_magnet)
+ self.magnet_entry.set_editable(False)
+ self.magnet_entry.connect('copy-clipboard', self.on_copy_emitted)
+ table.attach(self.magnet_entry, 0, 1, 0, 1)
+
+ copy_button = Gtk.Button.new_with_label(_('Copy'))
+ copy_button.connect('clicked', self.on_copy_clicked)
+ table.attach(copy_button, 1, 2, 0, 1)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ self.set_focus(self.magnet_entry)
+
+ self.show_all()
+
+ def on_copy_clicked(self, widget):
+ self.magnet_entry.select_region(0, -1)
+ self.magnet_entry.copy_clipboard()
+ self.magnet_entry.set_position(0)
+ self.copied = True
+
+ def on_copy_emitted(self, widget):
+ self.copied = True
diff --git a/deluge/ui/gtk3/edittrackersdialog.py b/deluge/ui/gtk3/edittrackersdialog.py
index a21a7d71f..861e3924b 100644
--- a/deluge/ui/gtk3/edittrackersdialog.py
+++ b/deluge/ui/gtk3/edittrackersdialog.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os.path
@@ -77,7 +74,7 @@ def trackers_tiers_from_text(text_str=''):
return trackers
-class EditTrackersDialog(object):
+class EditTrackersDialog:
def __init__(self, torrent_id, parent=None):
self.torrent_id = torrent_id
self.builder = Gtk.Builder()
@@ -134,6 +131,11 @@ class EditTrackersDialog(object):
self.dialog.connect('response', self._on_response)
self.treeview.connect('button_press_event', self.on_button_press_event)
+ self.add_tracker_dialog.connect('key-press-event', self.on_key_add_press_event)
+ self.add_tracker_dialog.connect('delete-event', self.on_delete_event_add)
+ self.edit_tracker_entry.connect('key-press-event', self.on_key_edit_press_event)
+ self.edit_tracker_entry.connect('delete-event', self.on_delete_event_edit)
+
def run(self):
# Make sure we have a torrent_id.. if not just return
if self.torrent_id is None:
@@ -192,7 +194,7 @@ class EditTrackersDialog(object):
self.old_trackers = list(status['trackers'])
for tracker in self.old_trackers:
self.add_tracker(tracker['tier'], tracker['url'])
- self.treeview.set_cursor((0))
+ self.treeview.set_cursor(0)
self.dialog.show()
def add_tracker(self, tier, url):
@@ -208,6 +210,7 @@ class EditTrackersDialog(object):
# Show the add tracker dialog
self.add_tracker_dialog.show()
self.builder.get_object('textview_trackers').grab_focus()
+ self.dialog.set_sensitive(False)
def on_button_remove_clicked(self, widget):
log.debug('on_button_remove_clicked')
@@ -236,11 +239,27 @@ class EditTrackersDialog(object):
self.edit_tracker_entry.grab_focus()
self.dialog.set_sensitive(False)
- def on_button_edit_cancel_clicked(self, widget):
- log.debug('on_button_edit_cancel_clicked')
+ def _close_edit_dialog(self):
self.dialog.set_sensitive(True)
self.edit_tracker_entry.hide()
+ def on_button_edit_cancel_clicked(self, widget):
+ """handles the cancel button"""
+ log.debug('on_button_edit_cancel_clicked')
+ self._close_edit_dialog()
+
+ def on_key_edit_press_event(self, widget, event):
+ """handles Escape key press"""
+ if event.keyval == Gdk.KEY_Escape:
+ log.debug('on_key_edit_press_event')
+ self._close_edit_dialog()
+
+ def on_delete_event_edit(self, widget, event):
+ """handles the Top-Right X button"""
+ log.debug('on_delete_event_edit')
+ self._close_edit_dialog()
+ return True
+
def on_button_edit_ok_clicked(self, widget):
log.debug('on_button_edit_ok_clicked')
selected = self.get_selected()
@@ -301,11 +320,29 @@ class EditTrackersDialog(object):
# Clear the entry widget and hide the dialog
textview_buf.set_text('')
+ self.dialog.set_sensitive(True)
self.add_tracker_dialog.hide()
- def on_button_add_cancel_clicked(self, widget):
- log.debug('on_button_add_cancel_clicked')
+ def _discard_and_close_add_dialog(self):
# Clear the entry widget and hide the dialog
b = Gtk.TextBuffer()
self.builder.get_object('textview_trackers').set_buffer(b)
+ self.dialog.set_sensitive(True)
self.add_tracker_dialog.hide()
+
+ def on_button_add_cancel_clicked(self, widget):
+ """handles the cancel button"""
+ log.debug('on_button_add_cancel_clicked')
+ self._discard_and_close_add_dialog()
+
+ def on_key_add_press_event(self, widget, event):
+ """handles Escape key press"""
+ if event.keyval == Gdk.KEY_Escape:
+ log.debug('on_key_add_press_event')
+ self._discard_and_close_add_dialog()
+
+ def on_delete_event_add(self, widget, event):
+ """handles the Top-Right X button"""
+ log.debug('on_delete_event_add')
+ self._discard_and_close_add_dialog()
+ return True
diff --git a/deluge/ui/gtk3/files_tab.py b/deluge/ui/gtk3/files_tab.py
index 50f8a4587..24c169727 100644
--- a/deluge/ui/gtk3/files_tab.py
+++ b/deluge/ui/gtk3/files_tab.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,15 +6,13 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import json
import logging
import os.path
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 Gio, Gtk
@@ -84,7 +81,7 @@ def cell_progress(column, cell, model, row, data):
class FilesTab(Tab):
def __init__(self):
- super(FilesTab, self).__init__('Files', 'files_tab', 'files_tab_label')
+ super().__init__('Files', 'files_tab', 'files_tab_label')
self.listview = self.main_builder.get_object('files_listview')
# filename, size, progress string, progress value, priority, file index, icon id
diff --git a/deluge/ui/gtk3/filtertreeview.py b/deluge/ui/gtk3/filtertreeview.py
index 4272ef018..40752d78c 100644
--- a/deluge/ui/gtk3/filtertreeview.py
+++ b/deluge/ui/gtk3/filtertreeview.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# 2008 Andrew Resch <andrewresch@gmail.com>
@@ -9,8 +8,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
import warnings
@@ -24,7 +21,7 @@ from deluge.common import TORRENT_STATE, decode_bytes, resource_filename
from deluge.configmanager import ConfigManager
from deluge.ui.client import client
-from .common import get_pixbuf, get_pixbuf_at_size
+from .common import get_pixbuf
log = logging.getLogger(__name__)
@@ -90,7 +87,7 @@ class FilterTreeView(component.Component):
self.treeview.set_level_indentation(-21)
# Force theme to use expander-size so we don't cut out entries due to indentation hack.
provider = Gtk.CssProvider()
- provider.load_from_data('* {-GtkTreeView-expander-size: 9;}'.encode())
+ provider.load_from_data(b'* {-GtkTreeView-expander-size: 9;}')
context = self.treeview.get_style_context()
context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
@@ -256,7 +253,7 @@ class FilterTreeView(component.Component):
return get_pixbuf('%s16.png' % pix)
def set_row_image(self, cat, value, filename):
- pix = get_pixbuf_at_size(filename, 16)
+ pix = get_pixbuf(filename, size=16)
row = self.filters[(cat, value)]
self.treestore.set_value(row, 4, pix)
return False
diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui
index a7a8caeaa..8adbad329 100644
--- a/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui
+++ b/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui
@@ -144,8 +144,6 @@
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="truncate_multiline">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/ui/gtk3/glade/add_torrent_dialog.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.ui
index 4d3680344..7183272e1 100644
--- a/deluge/ui/gtk3/glade/add_torrent_dialog.ui
+++ b/deluge/ui/gtk3/glade/add_torrent_dialog.ui
@@ -727,8 +727,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_maxup">
<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="update_policy">if-valid</property>
</object>
@@ -741,8 +739,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_maxconnections">
<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>
</object>
<packing>
@@ -754,8 +750,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_maxupslots">
<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>
</object>
<packing>
@@ -767,8 +761,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_maxdown">
<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>
diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui
index ecbd0f7cf..6b75b235f 100644
--- a/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui
+++ b/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui
@@ -143,8 +143,6 @@
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="truncate_multiline">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/ui/gtk3/glade/connect_peer_dialog.ui b/deluge/ui/gtk3/glade/connect_peer_dialog.ui
index f5e9337ea..4a60751d0 100644
--- a/deluge/ui/gtk3/glade/connect_peer_dialog.ui
+++ b/deluge/ui/gtk3/glade/connect_peer_dialog.ui
@@ -128,8 +128,6 @@
<property name="width_chars">39</property>
<property name="text" translatable="yes">hostname:port</property>
<property name="truncate_multiline">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/ui/gtk3/glade/connection_manager.addhost.ui b/deluge/ui/gtk3/glade/connection_manager.addhost.ui
index 641a71cc1..ea5376e45 100644
--- a/deluge/ui/gtk3/glade/connection_manager.addhost.ui
+++ b/deluge/ui/gtk3/glade/connection_manager.addhost.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1 -->
+<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.0"/>
<object class="GtkAdjustment" id="adjustment_port">
@@ -16,7 +16,7 @@
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
- <child>
+ <child type="titlebar">
<placeholder/>
</child>
<child internal-child="vbox">
@@ -24,11 +24,12 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
- <property name="spacing">2</property>
+ <property name="spacing">5</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog-action_area5">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="margin_top">15</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_addhost_cancel">
@@ -65,49 +66,41 @@
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
- <property name="position">0</property>
+ <property name="position">1</property>
</packing>
</child>
<child>
- <object class="GtkBox" id="hbox2">
+ <object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="spacing">5</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">5</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Hostname:</property>
+ <property name="xalign">0</property>
</object>
<packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
</packing>
</child>
<child>
- <object class="GtkAlignment" id="alignment4">
+ <object class="GtkEntry" id="entry_hostname">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="left_padding">1</property>
- <child>
- <object class="GtkEntry" id="entry_hostname">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- <property name="activates_default">True</property>
- <property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
- <signal name="paste-clipboard" handler="on_entry_host_paste_clipboard" swapped="no"/>
- </object>
- </child>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="truncate_multiline">True</property>
+ <signal name="paste-clipboard" handler="on_entry_host_paste_clipboard" swapped="no"/>
</object>
<packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
- <property name="position">1</property>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
</packing>
</child>
<child>
@@ -115,116 +108,90 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port:</property>
+ <property name="xalign">0</property>
</object>
<packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">2</property>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
+ <property name="halign">start</property>
<property name="max_length">5</property>
<property name="invisible_char">•</property>
<property name="width_chars">5</property>
+ <property name="max_width_chars">5</property>
<property name="progress_pulse_step">1</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_port</property>
<property name="climb_rate">1</property>
<property name="numeric">True</property>
</object>
<packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">3</property>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
</packing>
</child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkGrid" id="table1">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
<child>
- <object class="GtkAlignment" id="alignment3">
+ <object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="left_padding">5</property>
- <child>
- <object class="GtkEntry" id="entry_password">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="visibility">False</property>
- <property name="invisible_char">•</property>
- <property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
- </object>
- </child>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Username:</property>
+ <property name="xalign">0</property>
</object>
<packing>
- <property name="left_attach">1</property>
- <property name="top_attach">1</property>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
</packing>
</child>
<child>
- <object class="GtkAlignment" id="alignment2">
+ <object class="GtkEntry" id="entry_username">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="left_padding">5</property>
- <child>
- <object class="GtkEntry" id="entry_username">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- <property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
- </object>
- </child>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">•</property>
+ <property name="truncate_multiline">True</property>
</object>
<packing>
<property name="left_attach">1</property>
- <property name="top_attach">0</property>
+ <property name="top_attach">2</property>
</packing>
</child>
<child>
- <object class="GtkLabel" id="label5">
+ <object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
- <property name="label" translatable="yes">Username:</property>
+ <property name="label" translatable="yes">Password:</property>
+ <property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
- <property name="top_attach">0</property>
+ <property name="top_attach">3</property>
</packing>
</child>
<child>
- <object class="GtkLabel" id="label6">
+ <object class="GtkEntry" id="entry_password">
<property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="halign">start</property>
- <property name="label" translatable="yes">Password:</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">•</property>
+ <property name="truncate_multiline">True</property>
</object>
<packing>
- <property name="left_attach">0</property>
- <property name="top_attach">1</property>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">2</property>
+ <property name="position">0</property>
</packing>
</child>
</object>
diff --git a/deluge/ui/gtk3/glade/connection_manager.ui b/deluge/ui/gtk3/glade/connection_manager.ui
index 11516aa76..44f4b34e3 100644
--- a/deluge/ui/gtk3/glade/connection_manager.ui
+++ b/deluge/ui/gtk3/glade/connection_manager.ui
@@ -30,7 +30,7 @@
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">300</property>
- <property name="default_height">250</property>
+ <property name="default_height">285</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<child>
diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui
index dc7b7e93e..43283305a 100644
--- a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui
+++ b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui
@@ -143,8 +143,6 @@
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="truncate_multiline">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/ui/gtk3/glade/create_torrent_dialog.remote_save.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui
index a380718e4..712305489 100644
--- a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui
+++ b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui
@@ -143,8 +143,6 @@
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="truncate_multiline">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/ui/gtk3/glade/create_torrent_dialog.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.ui
index 0166e2d0b..0d1594014 100644
--- a/deluge/ui/gtk3/glade/create_torrent_dialog.ui
+++ b/deluge/ui/gtk3/glade/create_torrent_dialog.ui
@@ -356,8 +356,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">True</property>
@@ -393,8 +391,6 @@
<object class="GtkEntry" id="entry_comments">
<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="expand">True</property>
diff --git a/deluge/ui/gtk3/glade/edit_trackers.edit.ui b/deluge/ui/gtk3/glade/edit_trackers.edit.ui
index 2521e8ff8..fc3e51b83 100644
--- a/deluge/ui/gtk3/glade/edit_trackers.edit.ui
+++ b/deluge/ui/gtk3/glade/edit_trackers.edit.ui
@@ -144,8 +144,6 @@
<property name="invisible_char">•</property>
<property name="activates_default">True</property>
<property name="truncate_multiline">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/ui/gtk3/glade/main_window.tabs.ui b/deluge/ui/gtk3/glade/main_window.tabs.ui
index d4984dd9d..7ecf61821 100644
--- a/deluge/ui/gtk3/glade/main_window.tabs.ui
+++ b/deluge/ui/gtk3/glade/main_window.tabs.ui
@@ -1166,8 +1166,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">spin_stop_ratio_adjustment</property>
<property name="digits">1</property>
<property name="numeric">True</property>
@@ -1267,8 +1265,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">spin_max_connections_adjustment</property>
<property name="numeric">True</property>
</object>
@@ -1282,8 +1278,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">spin_max_upload_adjustment</property>
<property name="digits">1</property>
<property name="numeric">True</property>
@@ -1298,8 +1292,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">spin_max_download_adjustment</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
@@ -1356,8 +1348,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">spin_max_upload_slots_adjustment</property>
<property name="numeric">True</property>
</object>
diff --git a/deluge/ui/gtk3/glade/other_dialog.ui b/deluge/ui/gtk3/glade/other_dialog.ui
index 26d3d08bf..01a5597f2 100644
--- a/deluge/ui/gtk3/glade/other_dialog.ui
+++ b/deluge/ui/gtk3/glade/other_dialog.ui
@@ -148,8 +148,6 @@
<property name="max_length">6</property>
<property name="activates_default">True</property>
<property name="width_chars">6</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment1</property>
</object>
</child>
diff --git a/deluge/ui/gtk3/glade/path_combo_chooser.ui b/deluge/ui/gtk3/glade/path_combo_chooser.ui
index f79685d26..871bac01e 100644
--- a/deluge/ui/gtk3/glade/path_combo_chooser.ui
+++ b/deluge/ui/gtk3/glade/path_combo_chooser.ui
@@ -98,8 +98,6 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">2</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>
<property name="numeric">True</property>
@@ -571,6 +569,7 @@
<object class="GtkEntry" id="entry_text">
<property name="visible">True</property>
<property name="can_focus">True</property>
+ <property name="hexpand">True</property>
<property name="invisible_char">•</property>
<signal name="changed" handler="on_entry_text_changed" swapped="no"/>
<signal name="delete-text" handler="on_entry_text_delete_text" swapped="no"/>
diff --git a/deluge/ui/gtk3/glade/preferences_dialog.ui b/deluge/ui/gtk3/glade/preferences_dialog.ui
index 4b223cb44..aa1531d75 100644
--- a/deluge/ui/gtk3/glade/preferences_dialog.ui
+++ b/deluge/ui/gtk3/glade/preferences_dialog.ui
@@ -929,8 +929,6 @@ and daemon (does not apply in Standalone mode).</property>
<property name="width_chars">16</property>
<property name="text">********</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1150,6 +1148,7 @@ and daemon (does not apply in Standalone mode).</property>
<property name="top_padding">2</property>
<property name="bottom_padding">2</property>
<property name="left_padding">12</property>
+ <property name="right_padding">12</property>
<child>
<object class="GtkGrid" id="table9">
<property name="visible">True</property>
@@ -1545,8 +1544,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_max_connections_per_second">
<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">adjustment_spin_max_conn_per_sec</property>
<property name="numeric">True</property>
</object>
@@ -1559,8 +1556,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_max_half_open_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">adjustment_spin_max_half_open_conn</property>
<property name="numeric">True</property>
</object>
@@ -1624,8 +1619,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum number of connections allowed. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_conn_global</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
@@ -1655,8 +1648,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum download speed for all torrents. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_download</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
@@ -1672,8 +1663,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum upload speed for all torrents. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_upload</property>
<property name="climb_rate">1</property>
<property name="digits">1</property>
@@ -1689,8 +1678,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum upload slots for all torrents. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_upload_slots_global</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
@@ -1849,8 +1836,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum upload slots per torrent. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_upload_slots_per_torrent</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
@@ -1866,8 +1851,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_conn_per_torrent</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
@@ -1930,8 +1913,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum number download speed per torrent. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_download_per_torrent</property>
<property name="digits">1</property>
<property name="numeric">True</property>
@@ -1946,8 +1927,6 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">The maximum upload speed per torrent. Set -1 for unlimited.</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_max_upload_per_torrent</property>
<property name="digits">1</property>
<property name="numeric">True</property>
@@ -2131,8 +2110,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_active">
<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">adjustment_spin_active</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
@@ -2146,8 +2123,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_seeding">
<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">adjustment_spin_seeding</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
@@ -2185,8 +2160,6 @@ used sparingly.</property>
<object class="GtkSpinButton" id="spin_downloading">
<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">adjustment_spin_downloading</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
@@ -2323,8 +2296,6 @@ used sparingly.</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">adjustment_spin_seed_time_limit</property>
</object>
<packing>
@@ -2337,8 +2308,6 @@ used sparingly.</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">adjustment_time_ratio_limit</property>
<property name="digits">2</property>
</object>
@@ -2352,8 +2321,6 @@ used sparingly.</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">adjustment_share_ratio_limit</property>
<property name="digits">2</property>
</object>
@@ -2425,8 +2392,6 @@ used sparingly.</property>
<property name="sensitive">False</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">adjustment_share_ratio</property>
<property name="digits">2</property>
<property name="numeric">True</property>
@@ -2572,12 +2537,10 @@ used sparingly.</property>
<object class="GtkEntry" id="entry_interface">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="tooltip_text" translatable="yes">The IP address of the interface to listen for incoming bittorrent connections on. Leave this empty if you want to use the default.</property>
- <property name="max_length">15</property>
+ <property name="tooltip_text" translatable="yes">IP address or network interface name to listen for incoming BitTorrent connections. Leave empty to use system default.</property>
+ <property name="max_length">40</property>
<property name="width_chars">15</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
</child>
</object>
@@ -2586,7 +2549,7 @@ used sparingly.</property>
<object class="GtkLabel" id="label110">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="label" translatable="yes">Incoming Address</property>
+ <property name="label" translatable="yes">Incoming Interface</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
@@ -2628,8 +2591,6 @@ used sparingly.</property>
<property name="can_focus">True</property>
<property name="max_length">5</property>
<property name="max_width_chars">6</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_incoming_port</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
@@ -2728,6 +2689,24 @@ used sparingly.</property>
</packing>
</child>
<child>
+ <object class="GtkAlignment" id="alignment31">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkSpinner" id="port_spinner">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkAlignment" id="alignment48">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -2742,7 +2721,7 @@ used sparingly.</property>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">2</property>
+ <property name="position">3</property>
</packing>
</child>
</object>
@@ -2793,14 +2772,12 @@ used sparingly.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">
-The network interface name or IP address for outgoing BitTorrent connections. (Leave empty for default.)
+ IP address or network interface name for outgoing BitTorrent connections. Leave empty to use system default.
</property>
- <property name="max_length">15</property>
+ <property name="max_length">40</property>
<property name="invisible_char">●</property>
<property name="width_chars">15</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
</child>
</object>
@@ -2880,8 +2857,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L
<property name="can_focus">True</property>
<property name="max_length">5</property>
<property name="width_chars">7</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_outgoing_port_min</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
@@ -2913,8 +2888,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L
<property name="can_focus">True</property>
<property name="max_length">5</property>
<property name="width_chars">7</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_outgoing_port_max</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
@@ -3236,8 +3209,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L
<property name="width_chars">1</property>
<property name="text">0x00</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -3345,8 +3316,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L
<property name="can_focus">True</property>
<property name="visibility">False</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -3370,8 +3339,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<signal name="paste-clipboard" handler="on_entry_proxy_host_paste_clipboard" swapped="no"/>
</object>
<packing>
@@ -3399,8 +3366,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L
<object class="GtkSpinButton" id="spin_proxy_port">
<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">adjustment_spin_proxy_port</property>
<property name="numeric">True</property>
</object>
@@ -3416,8 +3381,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
@@ -3696,8 +3659,6 @@ the proxy instead of using the local DNS service</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">adjustment_cache_size</property>
<property name="numeric">True</property>
<property name="update_policy">if-valid</property>
@@ -3713,8 +3674,6 @@ the proxy instead of using the local DNS service</property>
<property name="can_focus">True</property>
<property name="max_length">5</property>
<property name="invisible_char">●</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_cache_expiry</property>
</object>
<packing>
@@ -4248,8 +4207,6 @@ the proxy instead of using the local DNS service</property>
<property name="tooltip_text" translatable="yes">If Deluge cannot find the database file at this location it will fallback to using DNS to resolve the peer's country.</property>
<property name="invisible_char">●</property>
<property name="truncate_multiline">True</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -4427,8 +4384,6 @@ the proxy instead of using the local DNS service</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_width_chars">6</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
<property name="adjustment">adjustment_spin_daemon_port</property>
</object>
<packing>
diff --git a/deluge/ui/gtk3/glade/torrent_menu.ui b/deluge/ui/gtk3/glade/torrent_menu.ui
index c1b77b4ad..c9ee289c1 100644
--- a/deluge/ui/gtk3/glade/torrent_menu.ui
+++ b/deluge/ui/gtk3/glade/torrent_menu.ui
@@ -31,6 +31,11 @@
<property name="icon_name">media-playback-pause-symbolic</property>
<property name="icon_size">1</property>
</object>
+ <object class="GtkImage" id="menu-item-image15">
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-copy-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
<object class="GtkImage" id="menu-item-image19">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -155,6 +160,17 @@
</object>
</child>
<child>
+ <object class="GtkImageMenuItem" id="menuitem_copymagnet">
+ <property name="label" translatable="yes">_Copy Magnet URI</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image15</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_copymagnet_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
<object class="GtkImageMenuItem" id="menuitem_updatetracker">
<property name="label" translatable="yes">_Update Tracker</property>
<property name="visible">True</property>
diff --git a/deluge/ui/gtk3/gtkui.py b/deluge/ui/gtk3/gtkui.py
index 02b309078..ddb2eb529 100644
--- a/deluge/ui/gtk3/gtkui.py
+++ b/deluge/ui/gtk3/gtkui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -8,8 +7,6 @@
#
# pylint: disable=wrong-import-position
-from __future__ import division, unicode_literals
-
import logging
import os
import signal
@@ -18,8 +15,8 @@ import time
import gi # isort:skip (Required before Gtk import).
-gi.require_version('Gtk', '3.0') # NOQA: E402
-gi.require_version('Gdk', '3.0') # NOQA: E402
+gi.require_version('Gtk', '3.0')
+gi.require_version('Gdk', '3.0')
# isort:imports-thirdparty
from gi.repository.GLib import set_prgname
@@ -32,7 +29,7 @@ try:
# Install twisted reactor, before any other modules import reactor.
reactor = gtk3reactor.install()
except ReactorAlreadyInstalledError:
- # Running unit tests so trial already installed a rector
+ # Running unit tests so already installed a rector
from twisted.internet import reactor
# isort:imports-firstparty
@@ -140,7 +137,7 @@ DEFAULT_PREFS = {
}
-class GtkUI(object):
+class GtkUI:
def __init__(self, args):
# Setup gtkbuilder/glade translation
setup_translation()
diff --git a/deluge/ui/gtk3/ipcinterface.py b/deluge/ui/gtk3/ipcinterface.py
index 78858c443..0ef28d8c0 100644
--- a/deluge/ui/gtk3/ipcinterface.py
+++ b/deluge/ui/gtk3/ipcinterface.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2009 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,14 +6,14 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
import sys
from base64 import b64encode
from glob import glob
from tempfile import mkstemp
+from urllib.parse import urlparse
+from urllib.request import url2pathname
import rencode
import twisted.internet.error
@@ -26,14 +25,6 @@ from deluge.common import decode_bytes, is_magnet, is_url, windows_check
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.ui.client import client
-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
-
log = logging.getLogger(__name__)
diff --git a/deluge/ui/gtk3/listview.py b/deluge/ui/gtk3/listview.py
index 4e9fe5db8..e9f6b1084 100644
--- a/deluge/ui/gtk3/listview.py
+++ b/deluge/ui/gtk3/listview.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,20 +6,18 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from gi.repository import GObject, Gtk
-from deluge.common import PY2, decode_bytes
+from deluge.common import decode_bytes
from .common import cmp, load_pickled_state_file, save_pickled_state_file
log = logging.getLogger(__name__)
-class ListViewColumnState(object):
+class ListViewColumnState:
"""Class used for saving/loading column state."""
def __init__(self, name, position, width, visible, sort, sort_order):
@@ -32,13 +29,13 @@ class ListViewColumnState(object):
self.sort_order = sort_order
-class ListView(object):
+class ListView:
"""ListView is used to make custom GtkTreeViews. It supports the adding
and removing of columns, creating a menu for a column toggle list and
support for 'status_field's which are used while updating the columns data.
"""
- class ListViewColumn(object):
+ class ListViewColumn:
"""Holds information regarding a column in the ListView"""
def __init__(self, name, column_indices):
@@ -66,7 +63,7 @@ class ListView(object):
self.pixbuf_index = 0
self.data_func = None
- class TreeviewColumn(Gtk.TreeViewColumn, object):
+ class TreeviewColumn(Gtk.TreeViewColumn):
"""
TreeViewColumn does not signal right-click events, and we need them
This subclass is equivalent to TreeViewColumn, but it signals these events
@@ -75,13 +72,11 @@ class ListView(object):
"""
__gsignals__ = {
- 'button-press-event'
- if not PY2
- else b'button-press-event': (GObject.SIGNAL_RUN_LAST, None, (object,))
+ 'button-press-event': (GObject.SIGNAL_RUN_LAST, None, (object,))
}
def __init__(self, title=None, cell_renderer=None, **args):
- """ Constructor, see Gtk.TreeViewColumn """
+ """Constructor, see Gtk.TreeViewColumn"""
Gtk.TreeViewColumn.__init__(self, title, cell_renderer, **args)
label = Gtk.Label(label=title)
self.set_widget(label)
diff --git a/deluge/ui/gtk3/mainwindow.py b/deluge/ui/gtk3/mainwindow.py
index b7d751410..d11ff317a 100644
--- a/deluge/ui/gtk3/mainwindow.py
+++ b/deluge/ui/gtk3/mainwindow.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
-
import logging
import os.path
from hashlib import sha1 as sha
@@ -45,7 +42,7 @@ if windowing('X11'):
log = logging.getLogger(__name__)
-class _GtkBuilderSignalsHolder(object):
+class _GtkBuilderSignalsHolder:
def connect_signals(self, mapping_or_class):
if isinstance(mapping_or_class, dict):
@@ -341,7 +338,9 @@ class MainWindow(component.Component):
return
self.previous_clipboard_text = text
if text and (
- (is_url(text) and text.endswith('.torrent')) or is_magnet(text)
+ (is_url(text) and text.endswith('.torrent'))
+ or is_magnet(text)
+ and not component.get('MenuBar').magnet_copied()
):
component.get('AddTorrentDialog').show()
component.get('AddTorrentDialog').on_button_url_clicked(window)
diff --git a/deluge/ui/gtk3/menubar.py b/deluge/ui/gtk3/menubar.py
index e09f394fc..a812a8cac 100644
--- a/deluge/ui/gtk3/menubar.py
+++ b/deluge/ui/gtk3/menubar.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@@ -9,8 +8,6 @@
#
-from __future__ import unicode_literals
-
import logging
import os.path
@@ -21,7 +18,7 @@ import deluge.component as component
from deluge.configmanager import ConfigManager
from deluge.ui.client import client
-from .dialogs import ErrorDialog, OtherDialog
+from .dialogs import CopyMagnetDialog, ErrorDialog, OtherDialog
from .path_chooser import PathChooser
log = logging.getLogger(__name__)
@@ -34,6 +31,7 @@ class MenuBar(component.Component):
self.mainwindow = component.get('MainWindow')
self.main_builder = self.mainwindow.get_builder()
self.config = ConfigManager('gtk3ui.conf')
+ self._magnet_copied = False
self.builder = Gtk.Builder()
# Get the torrent menu from the gtk builder file
@@ -142,6 +140,19 @@ class MenuBar(component.Component):
self.change_sensitivity = ['menuitem_addtorrent']
+ def magnet_copied(self):
+ """
+ lets the caller know whether a magnet was copied internally
+
+ the `mainwindow` checks every time the data in the clipboard,
+ so it will automatically open the AddTorrentURL dialog in case it
+ contains a valid link (URL to a torrent or a magnet URI).
+
+ """
+ val = self._magnet_copied
+ self._magnet_copied = False
+ return val
+
def start(self):
for widget in self.change_sensitivity:
self.main_builder.get_object(widget).set_sensitive(True)
@@ -282,6 +293,21 @@ class MenuBar(component.Component):
component.get('TorrentView').get_selected_torrents()
)
+ def on_menuitem_copymagnet_activate(self, data=None):
+ log.debug('on_menuitem_copymagnet_activate')
+ torrent_ids = component.get('TorrentView').get_selected_torrents()
+ if torrent_ids:
+
+ def _on_magnet_uri(magnet_uri):
+ def update_copied(response_id):
+ if dialog.copied:
+ self._magnet_copied = True
+
+ dialog = CopyMagnetDialog(magnet_uri)
+ dialog.run().addCallback(update_copied)
+
+ client.core.get_magnet_uri(torrent_ids[0]).addCallback(_on_magnet_uri)
+
def on_menuitem_updatetracker_activate(self, data=None):
log.debug('on_menuitem_updatetracker_activate')
client.core.force_reannounce(
@@ -541,7 +567,7 @@ class MenuBar(component.Component):
account_to_log = {}
for key, value in account.copy().items():
if key == 'password':
- value = '*' * len(value)
+ value = '*' * 10
account_to_log[key] = value
known_accounts_to_log.append(account_to_log)
log.debug('_on_known_accounts: %s', known_accounts_to_log)
diff --git a/deluge/ui/gtk3/menubar_osx.py b/deluge/ui/gtk3/menubar_osx.py
index 1df6fab08..53150fbf3 100644
--- a/deluge/ui/gtk3/menubar_osx.py
+++ b/deluge/ui/gtk3/menubar_osx.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 gi.repository.Gdk import ModifierType
from gi.repository.Gtk import SeparatorMenuItem, accel_groups_from_object
from gi.repository.Gtk.AccelFlags import VISIBLE
diff --git a/deluge/ui/gtk3/new_release_dialog.py b/deluge/ui/gtk3/new_release_dialog.py
index 6aa328260..a635bd2cd 100644
--- a/deluge/ui/gtk3/new_release_dialog.py
+++ b/deluge/ui/gtk3/new_release_dialog.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from gi.repository.Gtk import IconSize
import deluge.common
@@ -17,7 +14,7 @@ from deluge.configmanager import ConfigManager
from deluge.ui.client import client
-class NewReleaseDialog(object):
+class NewReleaseDialog:
def __init__(self):
pass
diff --git a/deluge/ui/gtk3/options_tab.py b/deluge/ui/gtk3/options_tab.py
index 6a25fd1e8..b0411a8b6 100644
--- a/deluge/ui/gtk3/options_tab.py
+++ b/deluge/ui/gtk3/options_tab.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
# 2017 Calum Lind <calumlind+deluge@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from gi.repository.Gdk import keyval_name
import deluge.component as component
@@ -21,7 +18,7 @@ from .torrentdetails import Tab
class OptionsTab(Tab):
def __init__(self):
- super(OptionsTab, self).__init__('Options', 'options_tab', 'options_tab_label')
+ super().__init__('Options', 'options_tab', 'options_tab_label')
self.prev_torrent_ids = None
self.prev_status = None
@@ -191,8 +188,9 @@ class OptionsTab(Tab):
):
options[status_key] = widget_value
- if options.get('move_completed', False):
- options['move_completed_path'] = self.move_completed_path_chooser.get_text()
+ move_completed_path = self.move_completed_path_chooser.get_text()
+ if move_completed_path != self.prev_status['move_completed_path']:
+ options['move_completed_path'] = move_completed_path
client.core.set_torrent_options(self.prev_torrent_ids, options)
self.button_apply.set_sensitive(False)
diff --git a/deluge/ui/gtk3/path_chooser.py b/deluge/ui/gtk3/path_chooser.py
index b7228415e..805819660 100644
--- a/deluge/ui/gtk3/path_chooser.py
+++ b/deluge/ui/gtk3/path_chooser.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Bro <bro.development@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import deluge.component as component
@@ -125,7 +122,7 @@ class PathChoosersHandler(component.Component):
class PathChooser(PathChooserComboBox):
def __init__(self, paths_config_key=None, parent=None):
self.paths_config_key = paths_config_key
- super(PathChooser, self).__init__(parent=parent)
+ super().__init__(parent=parent)
self.chooser_handler = PathChoosersHandler()
self.chooser_handler.register_chooser(self)
self.set_auto_completer_func(self.on_completion)
diff --git a/deluge/ui/gtk3/path_combo_chooser.py b/deluge/ui/gtk3/path_combo_chooser.py
index 72e98e497..74d9055b7 100755
--- a/deluge/ui/gtk3/path_combo_chooser.py
+++ b/deluge/ui/gtk3/path_combo_chooser.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2013 Bro <bro.development@gmail.com>
#
@@ -8,15 +7,13 @@
# See LICENSE for more details.
#
-from __future__ import division, print_function, unicode_literals
-
import os
import warnings
from gi.repository import Gdk, GObject, Gtk
from gi.repository.GObject import SignalFlags
-from deluge.common import PY2, resource_filename
+from deluge.common import resource_filename
from deluge.path_chooser_common import get_completion_paths
# Filter the pygobject signal warning:
@@ -64,7 +61,7 @@ def path_without_trailing_path_sep(path):
return path
-class ValueList(object):
+class ValueList:
paths_without_trailing_path_sep = False
@@ -176,7 +173,7 @@ class ValueList(object):
"""
for i, row in enumerate(self.tree_store):
if row[0] == value:
- self.treeview.set_cursor((i))
+ self.treeview.set_cursor(i)
return
# The value was not found
if select_first:
@@ -374,7 +371,7 @@ class StoredValuesList(ValueList):
"""
# This is left click
if event.button != 3:
- super(StoredValuesList, self).on_treeview_mouse_button_press_event(
+ super().on_treeview_mouse_button_press_event(
treeview, event, double_click=True
)
return False
@@ -412,9 +409,7 @@ class StoredValuesList(ValueList):
PathChooserPopup.popup(self)
def on_stored_values_treeview_key_press_event(self, widget, event):
- super(StoredValuesList, self).on_value_list_treeview_key_press_event(
- widget, event
- )
+ super().on_value_list_treeview_key_press_event(widget, event)
# Prevent the default event handler to move the cursor in the list
if key_is_up_or_down(event.keyval):
return True
@@ -479,9 +474,9 @@ class CompletionList(ValueList):
] = self.on_completion_treeview_motion_notify_event
# Add super class signal handler
- self.signal_handlers['on_completion_treeview_mouse_button_press_event'] = super(
- CompletionList, self
- ).on_treeview_mouse_button_press_event
+ self.signal_handlers[
+ 'on_completion_treeview_mouse_button_press_event'
+ ] = super().on_treeview_mouse_button_press_event
def reduce_values(self, prefix):
"""
@@ -499,9 +494,7 @@ class CompletionList(ValueList):
self.add_values(matching_values, clear=True)
def on_completion_treeview_key_press_event(self, widget, event):
- ret = super(CompletionList, self).on_value_list_treeview_key_press_event(
- widget, event
- )
+ ret = super().on_value_list_treeview_key_press_event(widget, event)
if ret:
return ret
keyval = event.keyval
@@ -529,7 +522,7 @@ class CompletionList(ValueList):
self.handle_list_scroll(path=path[0], _next=None)
-class PathChooserPopup(object):
+class PathChooserPopup:
"""This creates the popop window for the ComboEntry."""
def __init__(self, min_visible_rows, max_visible_rows, popup_alignment_widget):
@@ -983,7 +976,7 @@ class PathCompletionPopup(CompletionList, PathChooserPopup):
return True
-class PathAutoCompleter(object):
+class PathAutoCompleter:
def __init__(self, builder, path_entry, max_visible_rows):
self.completion_popup = PathCompletionPopup(
builder, path_entry, max_visible_rows
@@ -1106,9 +1099,7 @@ class PathAutoCompleter(object):
class PathChooserComboBox(Gtk.Box, StoredValuesPopup, GObject.GObject):
__gsignals__ = {
- signal
- if not PY2
- else signal.encode(): (SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,))
+ signal: (SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,))
for signal in [
'text-changed',
'accelerator-set',
@@ -1414,7 +1405,7 @@ class PathChooserComboBox(Gtk.Box, StoredValuesPopup, GObject.GObject):
self.set_text(self.get_text())
def _on_entry_combobox_hbox_realize(self, widget):
- """ Must do this when the widget is realized """
+ """Must do this when the widget is realized"""
self.set_filechooser_button_visible(self.filechooser_visible)
self.set_path_entry_visible(self.path_entry_visible)
@@ -1466,7 +1457,7 @@ class PathChooserComboBox(Gtk.Box, StoredValuesPopup, GObject.GObject):
)
return True
elif is_ascii_value(keyval, 's'):
- super(PathChooserComboBox, self).add_current_value_to_saved_list()
+ super().add_current_value_to_saved_list()
return True
elif is_ascii_value(keyval, 'd'):
# Set the default value in the text entry
@@ -1696,7 +1687,7 @@ if __name__ == '__main__':
box1 = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=0)
def get_resource2(filename):
- return '%s/glade/%s' % (os.path.abspath(os.path.dirname(sys.argv[0])), filename)
+ return f'{os.path.abspath(os.path.dirname(sys.argv[0]))}/glade/{filename}'
# Override get_resource which fetches from deluge install
# get_resource = get_resource2
diff --git a/deluge/ui/gtk3/peers_tab.py b/deluge/ui/gtk3/peers_tab.py
index e0282becf..b458f7a7d 100644
--- a/deluge/ui/gtk3/peers_tab.py
+++ b/deluge/ui/gtk3/peers_tab.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os.path
@@ -42,18 +39,12 @@ from .torrentview_data_funcs import (
cell_data_speed_up,
)
-try:
- from future_builtins import zip
-except ImportError:
- # Ignore on Py3.
- pass
-
log = logging.getLogger(__name__)
class PeersTab(Tab):
def __init__(self):
- super(PeersTab, self).__init__('Peers', 'peers_tab', 'peers_tab_label')
+ super().__init__('Peers', 'peers_tab', 'peers_tab_label')
self.peer_menu = self.main_builder.get_object('menu_peer_tab')
component.get('MainWindow').connect_signals(self)
@@ -313,7 +304,7 @@ class PeersTab(Tab):
ip_int = int(
binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16
)
- peer_ip = '[%s]:%s' % (ip, peer['ip'].split(':')[-1])
+ peer_ip = '[{}]:{}'.format(ip, peer['ip'].split(':')[-1])
if peer['seed']:
icon = self.seed_pixbuf
diff --git a/deluge/ui/gtk3/piecesbar.py b/deluge/ui/gtk3/piecesbar.py
index 549f9c048..8665328c0 100644
--- a/deluge/ui/gtk3/piecesbar.py
+++ b/deluge/ui/gtk3/piecesbar.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
@@ -7,15 +6,13 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
from math import pi
import gi # isort:skip (Version check required before import).
-gi.require_version('PangoCairo', '1.0') # NOQA: E402
-gi.require_foreign('cairo') # NOQA: E402
-gi.require_version('cairo', '1.0') # NOQA: E402
+gi.require_version('PangoCairo', '1.0')
+gi.require_foreign('cairo')
+gi.require_version('cairo', '1.0')
# isort:imports-thirdparty
import cairo # Backward compat cairo <= 1.15
@@ -24,7 +21,6 @@ from gi.repository.Gtk import DrawingArea, ProgressBar, StateFlags
from gi.repository.Pango import SCALE, Weight
# isort:imports-firstparty
-from deluge.common import PY2
from deluge.configmanager import ConfigManager
COLOR_STATES = ['missing', 'waiting', 'downloading', 'completed']
@@ -32,10 +28,10 @@ COLOR_STATES = ['missing', 'waiting', 'downloading', 'completed']
class PiecesBar(DrawingArea):
# Draw in response to an draw
- __gsignals__ = {'draw': 'override'} if not PY2 else {b'draw': b'override'}
+ __gsignals__ = {'draw': 'override'}
def __init__(self):
- super(PiecesBar, self).__init__()
+ super().__init__()
# Get progress bar styles, in order to keep font consistency
pb = ProgressBar()
pb_style = pb.get_style_context()
diff --git a/deluge/ui/gtk3/pluginmanager.py b/deluge/ui/gtk3/pluginmanager.py
index d60f8d390..63353c0df 100644
--- a/deluge/ui/gtk3/pluginmanager.py
+++ b/deluge/ui/gtk3/pluginmanager.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 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/ui/gtk3/preferences.py b/deluge/ui/gtk3/preferences.py
index 13930fc55..a008a9562 100644
--- a/deluge/ui/gtk3/preferences.py
+++ b/deluge/ui/gtk3/preferences.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
@@ -8,11 +7,10 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
from hashlib import sha1 as sha
+from urllib.parse import urlparse
from gi import require_version
from gi.repository import Gtk
@@ -21,6 +19,7 @@ from gi.repository.Gdk import Color
import deluge.common
import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir
+from deluge.decorators import maybe_coroutine
from deluge.error import AuthManagerError, NotAuthorizedError
from deluge.i18n import get_languages
from deluge.ui.client import client
@@ -31,12 +30,6 @@ from .dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog
from .path_chooser import PathChooser
try:
- from urllib.parse import urlparse
-except ImportError:
- # PY2 fallback
- from urlparse import urlparse # pylint: disable=ungrouped-imports
-
-try:
require_version('AppIndicator3', '0.1')
from gi.repository import AppIndicator3 # noqa: F401
except (ImportError, ValueError):
@@ -679,11 +672,15 @@ class Preferences(component.Component):
'chk_random_outgoing_ports'
).get_active()
incoming_address = self.builder.get_object('entry_interface').get_text().strip()
- if deluge.common.is_ip(incoming_address) or not incoming_address:
+ if deluge.common.is_interface(incoming_address) or not incoming_address:
new_core_config['listen_interface'] = incoming_address
- new_core_config['outgoing_interface'] = (
+ outgoing_address = (
self.builder.get_object('entry_outgoing_interface').get_text().strip()
)
+ if deluge.common.is_interface(outgoing_address) or not outgoing_address:
+ new_core_config['outgoing_interface'] = (
+ self.builder.get_object('entry_outgoing_interface').get_text().strip()
+ )
new_core_config['peer_tos'] = self.builder.get_object(
'entry_peer_tos'
).get_text()
@@ -948,6 +945,7 @@ class Preferences(component.Component):
def hide(self):
self.window_open = False
+ self.builder.get_object('port_spinner').stop()
self.builder.get_object('port_img').hide()
self.pref_dialog.hide()
@@ -1092,6 +1090,8 @@ class Preferences(component.Component):
log.debug('on_test_port_clicked')
def on_get_test(status):
+ self.builder.get_object('port_spinner').stop()
+ self.builder.get_object('port_spinner').hide()
if status:
self.builder.get_object('port_img').set_from_icon_name(
'emblem-ok-symbolic', Gtk.IconSize.MENU
@@ -1104,12 +1104,9 @@ class Preferences(component.Component):
self.builder.get_object('port_img').show()
client.core.test_listen_port().addCallback(on_get_test)
- # XXX: Consider using gtk.Spinner() instead of the loading gif
- # It requires gtk.ver > 2.12
- self.builder.get_object('port_img').set_from_file(
- deluge.common.get_pixmap('loading.gif')
- )
- self.builder.get_object('port_img').show()
+ self.builder.get_object('port_spinner').start()
+ self.builder.get_object('port_spinner').show()
+ self.builder.get_object('port_img').hide()
client.force_call()
def on_plugin_toggled(self, renderer, path):
@@ -1333,58 +1330,46 @@ class Preferences(component.Component):
(model, itr) = treeselection.get_selected()
if not itr:
return
- username = model[itr][0]
- if username:
+ level = model[itr][1]
+ if level:
self.builder.get_object('accounts_edit').set_sensitive(True)
self.builder.get_object('accounts_delete').set_sensitive(True)
else:
self.builder.get_object('accounts_edit').set_sensitive(False)
self.builder.get_object('accounts_delete').set_sensitive(False)
- def on_accounts_add_clicked(self, widget):
+ @maybe_coroutine
+ async def on_accounts_add_clicked(self, widget):
dialog = AccountDialog(
levels_mapping=client.auth_levels_mapping, parent=self.pref_dialog
)
+ response = await dialog.run()
+ if response != Gtk.ResponseType.OK:
+ return
- def dialog_finished(response_id):
- username = dialog.get_username()
- password = dialog.get_password()
- authlevel = dialog.get_authlevel()
-
- def add_ok(rv):
- accounts_iter = self.accounts_liststore.append()
- self.accounts_liststore.set_value(
- accounts_iter, ACCOUNTS_USERNAME, username
- )
- self.accounts_liststore.set_value(
- accounts_iter, ACCOUNTS_LEVEL, authlevel
- )
- self.accounts_liststore.set_value(
- accounts_iter, ACCOUNTS_PASSWORD, password
- )
-
- def add_fail(failure):
- if failure.type == AuthManagerError:
- ErrorDialog(
- _('Error Adding Account'),
- _('Authentication failed'),
- parent=self.pref_dialog,
- details=failure.getErrorMessage(),
- ).run()
- else:
- ErrorDialog(
- _('Error Adding Account'),
- _('An error occurred while adding account'),
- parent=self.pref_dialog,
- details=failure.getErrorMessage(),
- ).run()
-
- if response_id == Gtk.ResponseType.OK:
- client.core.create_account(username, password, authlevel).addCallback(
- add_ok
- ).addErrback(add_fail)
-
- dialog.run().addCallback(dialog_finished)
+ account = dialog.account
+ try:
+ await client.core.create_account(*account)
+ except AuthManagerError as ex:
+ return ErrorDialog(
+ _('Error Adding Account'),
+ _('Authentication failed'),
+ parent=self.pref_dialog,
+ details=ex,
+ ).run()
+ except Exception as ex:
+ return ErrorDialog(
+ _('Error Adding Account'),
+ _(f'An error occurred while adding account: {account}'),
+ parent=self.pref_dialog,
+ details=ex,
+ ).run()
+
+ self.accounts_liststore.set(
+ self.accounts_liststore.append(),
+ [ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD],
+ [account.username, account.authlevel, account.password],
+ )
def on_accounts_edit_clicked(self, widget):
(model, itr) = self.accounts_listview.get_selection().get_selected()
diff --git a/deluge/ui/gtk3/queuedtorrents.py b/deluge/ui/gtk3/queuedtorrents.py
index 0f08c24c6..6fdecec76 100644
--- a/deluge/ui/gtk3/queuedtorrents.py
+++ b/deluge/ui/gtk3/queuedtorrents.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.path
diff --git a/deluge/ui/gtk3/removetorrentdialog.py b/deluge/ui/gtk3/removetorrentdialog.py
index 48806a5d5..06fca7704 100644
--- a/deluge/ui/gtk3/removetorrentdialog.py
+++ b/deluge/ui/gtk3/removetorrentdialog.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
@@ -21,7 +18,7 @@ from deluge.ui.client import client
log = logging.getLogger(__name__)
-class RemoveTorrentDialog(object):
+class RemoveTorrentDialog:
"""
This class is used to create and show a Remove Torrent Dialog.
diff --git a/deluge/ui/gtk3/sidebar.py b/deluge/ui/gtk3/sidebar.py
index 1d751918f..5a2b15466 100644
--- a/deluge/ui/gtk3/sidebar.py
+++ b/deluge/ui/gtk3/sidebar.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
@@ -8,8 +7,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
from gi.repository.Gtk import Label, PolicyType, ScrolledWindow
diff --git a/deluge/ui/gtk3/status_tab.py b/deluge/ui/gtk3/status_tab.py
index 938c2dd7d..6a9010b6f 100644
--- a/deluge/ui/gtk3/status_tab.py
+++ b/deluge/ui/gtk3/status_tab.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
import deluge.component as component
@@ -32,7 +29,7 @@ log = logging.getLogger(__name__)
class StatusTab(Tab):
def __init__(self):
- super(StatusTab, self).__init__('Status', 'status_tab', 'status_tab_label')
+ super().__init__('Status', 'status_tab', 'status_tab_label')
self.config = ConfigManager('gtk3ui.conf')
diff --git a/deluge/ui/gtk3/statusbar.py b/deluge/ui/gtk3/statusbar.py
index 18db753fa..0a2e80095 100644
--- a/deluge/ui/gtk3/statusbar.py
+++ b/deluge/ui/gtk3/statusbar.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
import logging
from gi.repository import Gtk
@@ -25,7 +22,7 @@ from .dialogs import OtherDialog
log = logging.getLogger(__name__)
-class StatusBarItem(object):
+class StatusBarItem:
def __init__(
self,
image=None,
@@ -320,18 +317,22 @@ class StatusBar(component.Component):
def send_status_request(self):
# Sends an async request for data from the core
keys = [
- 'num_peers',
+ 'peer.num_peers_connected',
'upload_rate',
'download_rate',
'payload_upload_rate',
'payload_download_rate',
+ 'net.sent_bytes',
+ 'net.recv_bytes',
+ 'net.sent_payload_bytes',
+ 'net.recv_payload_bytes',
]
if self.dht_status:
- keys.append('dht_nodes')
+ keys.append('dht.dht_nodes')
if not self.health:
- keys.append('has_incoming_connections')
+ keys.append('net.has_incoming_connections')
client.core.get_session_status(keys).addCallback(self._on_get_session_status)
client.core.get_free_space().addCallback(self._on_get_free_space)
@@ -370,18 +371,18 @@ class StatusBar(component.Component):
self.upload_protocol_rate = (
status['upload_rate'] - status['payload_upload_rate']
) // 1024
- self.num_connections = status['num_peers']
+ self.num_connections = status['peer.num_peers_connected']
self.update_download_label()
self.update_upload_label()
self.update_traffic_label()
self.update_connections_label()
- if 'dht_nodes' in status:
- self.dht_nodes = status['dht_nodes']
+ if 'dht.dht_nodes' in status:
+ self.dht_nodes = status['dht.dht_nodes']
self.update_dht_label()
- if 'has_incoming_connections' in status:
- self.health = status['has_incoming_connections']
+ if 'net.has_incoming_connections' in status:
+ self.health = status['net.has_incoming_connections']
if self.health:
self.remove_item(self.health_item)
@@ -412,7 +413,7 @@ class StatusBar(component.Component):
if self.max_connections_global < 0:
label_string = '%s' % self.num_connections
else:
- label_string = '%s <small>(%s)</small>' % (
+ label_string = '{} <small>({})</small>'.format(
self.num_connections,
self.max_connections_global,
)
diff --git a/deluge/ui/gtk3/systemtray.py b/deluge/ui/gtk3/systemtray.py
index a2435223b..f65fde590 100644
--- a/deluge/ui/gtk3/systemtray.py
+++ b/deluge/ui/gtk3/systemtray.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
@@ -238,13 +235,13 @@ class SystemTray(component.Component):
if max_download_speed == -1:
max_download_speed = _('Unlimited')
else:
- max_download_speed = '%s %s' % (max_download_speed, _('K/s'))
+ max_download_speed = '{} {}'.format(max_download_speed, _('K/s'))
if max_upload_speed == -1:
max_upload_speed = _('Unlimited')
else:
- max_upload_speed = '%s %s' % (max_upload_speed, _('K/s'))
+ max_upload_speed = '{} {}'.format(max_upload_speed, _('K/s'))
- msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (
+ msg = '{}\n{}: {} ({})\n{}: {} ({})'.format(
_('Deluge'),
_('Down'),
self.download_rate,
diff --git a/deluge/ui/gtk3/tab_data_funcs.py b/deluge/ui/gtk3/tab_data_funcs.py
index 6fa0ba59c..a78994f69 100644
--- a/deluge/ui/gtk3/tab_data_funcs.py
+++ b/deluge/ui/gtk3/tab_data_funcs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,14 +6,12 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
from deluge.common import fdate, fsize, fspeed, ftime
from deluge.ui.common import TRACKER_STATUS_TRANSLATION
def ftotal_sized(first, second):
- return '%s (%s)' % (fsize(first, shortform=True), fsize(second, shortform=True))
+ return f'{fsize(first, shortform=True)} ({fsize(second, shortform=True)})'
def fratio(value):
@@ -24,7 +21,7 @@ def fratio(value):
def fpcnt(value, state, message):
state_i18n = _(state)
if state not in ('Error', 'Seeding') and value < 100:
- percent = '{:.2f}'.format(value).rstrip('0').rstrip('.')
+ percent = f'{value:.2f}'.rstrip('0').rstrip('.')
return _('{state} {percent}%').format(state=state_i18n, percent=percent)
elif state == 'Error':
return _('{state}: {err_msg}').format(state=state_i18n, err_msg=message)
@@ -34,7 +31,7 @@ def fpcnt(value, state, message):
def fspeed_max(value, max_value=-1):
value = fspeed(value, shortform=True)
- return '%s (%s %s)' % (value, max_value, _('K/s')) if max_value > -1 else value
+ return '{} ({} {})'.format(value, max_value, _('K/s')) if max_value > -1 else value
def fdate_or_never(value):
@@ -73,7 +70,7 @@ def fseed_rank_or_dash(seed_rank, seeding_time):
def fpieces_num_size(num_pieces, piece_size):
- return '%s (%s)' % (num_pieces, fsize(piece_size, precision=0))
+ return f'{num_pieces} ({fsize(piece_size, precision=0)})'
def fcount(value):
diff --git a/deluge/ui/gtk3/toolbar.py b/deluge/ui/gtk3/toolbar.py
index 7bc029e4b..1b6952e74 100644
--- a/deluge/ui/gtk3/toolbar.py
+++ b/deluge/ui/gtk3/toolbar.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
-
import logging
from gi.repository.Gtk import SeparatorToolItem, ToolButton
diff --git a/deluge/ui/gtk3/torrentdetails.py b/deluge/ui/gtk3/torrentdetails.py
index a586c2997..08c37a1de 100644
--- a/deluge/ui/gtk3/torrentdetails.py
+++ b/deluge/ui/gtk3/torrentdetails.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -9,8 +8,6 @@
"""The torrent details component shows info about the selected torrent."""
-from __future__ import unicode_literals
-
import logging
from collections import namedtuple
@@ -33,7 +30,7 @@ log = logging.getLogger(__name__)
TabWidget = namedtuple('TabWidget', ('obj', 'func', 'status_keys'))
-class Tab(object):
+class Tab:
def __init__(self, name=None, child_widget=None, tab_label=None):
self._name = name
self.is_visible = True
diff --git a/deluge/ui/gtk3/torrentview.py b/deluge/ui/gtk3/torrentview.py
index 3aee11e5c..16de16ea7 100644
--- a/deluge/ui/gtk3/torrentview.py
+++ b/deluge/ui/gtk3/torrentview.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -8,8 +7,6 @@
#
"""The torrent view component that lists all torrents in the session."""
-from __future__ import unicode_literals
-
import logging
from locale import strcoll
@@ -77,13 +74,13 @@ def eta_column_sort(model, iter1, iter2, data):
if v1 == v2:
return 0
if v1 == 0:
- return 1
- if v2 == 0:
return -1
- if v1 > v2:
+ if v2 == 0:
return 1
- if v2 > v1:
+ if v1 > v2:
return -1
+ if v2 > v1:
+ return 1
def seed_peer_column_sort(model, iter1, iter2, data):
@@ -107,7 +104,7 @@ def progress_sort(model, iter1, iter2, sort_column_id):
return cmp(progress1, progress2)
-class SearchBox(object):
+class SearchBox:
def __init__(self, torrentview):
self.torrentview = torrentview
mainwindow = component.get('MainWindow')
diff --git a/deluge/ui/gtk3/torrentview_data_funcs.py b/deluge/ui/gtk3/torrentview_data_funcs.py
index 8bd1f9c51..0b2545d8c 100644
--- a/deluge/ui/gtk3/torrentview_data_funcs.py
+++ b/deluge/ui/gtk3/torrentview_data_funcs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import print_function, unicode_literals
-
import warnings
from functools import partial
@@ -17,7 +14,7 @@ import deluge.component as component
from .common import (
create_blank_pixbuf,
- get_pixbuf_at_size,
+ get_pixbuf,
icon_alert,
icon_checking,
icon_downloading,
@@ -42,7 +39,6 @@ ICON_STATE = {
# renderer. This is much cheaper than fetch the current value and test if
# it's equal.
func_last_value = {
- 'cell_data_time': None,
'cell_data_ratio_seeds_peers': None,
'cell_data_ratio_ratio': None,
'cell_data_ratio_avail': None,
@@ -86,7 +82,7 @@ def set_tracker_icon(tracker_icon, cell):
if tracker_icon:
pixbuf = tracker_icon.get_cached_icon()
if pixbuf is None:
- pixbuf = get_pixbuf_at_size(tracker_icon.get_filename(), 16)
+ pixbuf = get_pixbuf(tracker_icon.get_filename(), 16)
tracker_icon.set_cached_icon(pixbuf)
else:
pixbuf = create_blank_pixbuf()
@@ -162,7 +158,7 @@ def cell_data_speed(cell, model, row, data):
if speed > 0:
speed_str = common.fspeed(speed, shortform=True)
cell.set_property(
- 'markup', '{0} <small>{1}</small>'.format(*tuple(speed_str.split()))
+ 'markup', '{} <small>{}</small>'.format(*tuple(speed_str.split()))
)
else:
cell.set_property('text', '')
@@ -189,7 +185,7 @@ def cell_data_speed_limit(cell, model, row, data, cache_key):
if speed > 0:
speed_str = common.fspeed(speed * 1024, shortform=True)
cell.set_property(
- 'markup', '{0} <small>{1}</small>'.format(*tuple(speed_str.split()))
+ 'markup', '{} <small>{}</small>'.format(*tuple(speed_str.split()))
)
else:
cell.set_property('text', '')
@@ -222,10 +218,6 @@ def cell_data_peer(column, cell, model, row, data):
def cell_data_time(column, cell, model, row, data):
"""Display value as time, eg 1m10s"""
time = model.get_value(row, data)
- if func_last_value['cell_data_time'] == time:
- return
- func_last_value['cell_data_time'] = time
-
if time <= 0:
time_str = ''
else:
diff --git a/deluge/ui/gtk3/trackers_tab.py b/deluge/ui/gtk3/trackers_tab.py
index d83b9956c..d671471b0 100644
--- a/deluge/ui/gtk3/trackers_tab.py
+++ b/deluge/ui/gtk3/trackers_tab.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 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
@@ -22,9 +19,7 @@ log = logging.getLogger(__name__)
class TrackersTab(Tab):
def __init__(self):
- super(TrackersTab, self).__init__(
- 'Trackers', 'trackers_tab', 'trackers_tab_label'
- )
+ super().__init__('Trackers', 'trackers_tab', 'trackers_tab_label')
self.add_tab_widget('summary_next_announce', ftime, ('next_announce',))
self.add_tab_widget('summary_tracker', None, ('tracker_host',))
diff --git a/deluge/ui/hostlist.py b/deluge/ui/hostlist.py
index b4bb538fb..0fc3eabd8 100644
--- a/deluge/ui/hostlist.py
+++ b/deluge/ui/hostlist.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) Calum Lind 2017 <calumlind+deluge@gmail.com>
#
@@ -7,12 +6,10 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
import uuid
-from socket import gaierror, gethostbyname
+from socket import gaierror, getaddrinfo
from twisted.internet import defer
@@ -25,7 +22,7 @@ log = logging.getLogger(__name__)
DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 58846
-LOCALHOST = ('127.0.0.1', 'localhost')
+LOCALHOST = ('127.0.0.1', 'localhost', '::1')
def default_hostlist():
@@ -47,7 +44,7 @@ def validate_host_info(hostname, port):
"""
try:
- gethostbyname(hostname)
+ getaddrinfo(hostname, None)
except gaierror as ex:
raise ValueError('Host %s: %s', hostname, ex.args[1])
@@ -87,7 +84,15 @@ def migrate_config_2_to_3(config):
return config
-class HostList(object):
+def mask_hosts_password(hosts):
+ """Replace passwords in hosts list with *'s for log output"""
+ if not hosts:
+ return hosts
+
+ return [list(host)[:-1] + ['*' * 10] for host in hosts]
+
+
+class HostList:
"""This class contains methods for adding, removing and looking up hosts in hostlist.conf."""
def __init__(self):
@@ -97,6 +102,7 @@ class HostList(object):
default_hostlist(),
config_dir=get_config_dir(),
file_version=3,
+ log_mask_funcs={'hosts': mask_hosts_password},
)
self.config.run_converter((1, 2), 3, migrate_config_2_to_3)
self.config.save()
@@ -210,30 +216,35 @@ class HostList(object):
return defer.succeed(status_offline)
try:
- ip = gethostbyname(host)
- except gaierror as ex:
- log.error('Error resolving host %s to ip: %s', host, ex.args[1])
+ ips = list({addrinfo[4][0] for addrinfo in getaddrinfo(host, None)})
+ except (gaierror, IndexError) as ex:
+ log.warning('Unable to resolve host %s to IP: %s', host, ex.args[1])
return defer.succeed(status_offline)
- host_conn_info = (
- ip,
- port,
- 'localclient' if not user and host in LOCALHOST else user,
- )
- if client.connected() and host_conn_info == client.connection_info():
- # Currently connected to host_id daemon.
- def on_info(info, host_id):
- log.debug('Client connected, query info: %s', info)
- return host_id, 'Connected', info
-
- return client.daemon.info().addCallback(on_info, host_id)
- else:
- # Attempt to connect to daemon with host_id details.
- c = Client()
- d = c.connect(host, port, skip_authentication=True)
- d.addCallback(on_connect, c, host_id)
- d.addErrback(on_connect_failed, host_id)
- return d
+ host_conn_list = [
+ (
+ host_ip,
+ port,
+ 'localclient' if not user and host_ip in LOCALHOST else user,
+ )
+ for host_ip in ips
+ ]
+
+ for host_conn_info in host_conn_list:
+ if client.connected() and host_conn_info == client.connection_info():
+ # Currently connected to host_id daemon.
+ def on_info(info, host_id):
+ log.debug('Client connected, query info: %s', info)
+ return host_id, 'Connected', info
+
+ return client.daemon.info().addCallback(on_info, host_id)
+ else:
+ # Attempt to connect to daemon with host_id details.
+ c = Client()
+ d = c.connect(host, port, skip_authentication=True)
+ d.addCallback(on_connect, c, host_id)
+ d.addErrback(on_connect_failed, host_id)
+ return d
def update_host(self, host_id, hostname, port, username, password):
"""Update the supplied host id with new connection details.
diff --git a/deluge/ui/sessionproxy.py b/deluge/ui/sessionproxy.py
index 5af8e79cd..b50ba6c3d 100644
--- a/deluge/ui/sessionproxy.py
+++ b/deluge/ui/sessionproxy.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 Andrew Resch <andrewresch@gmail.com>
#
@@ -6,8 +5,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 time import time
@@ -148,11 +145,17 @@ class SessionProxy(component.Component):
def on_status(result, torrent_id):
t = time()
- self.torrents[torrent_id][0] = t
- self.torrents[torrent_id][1].update(result)
- for key in keys_to_get:
- self.cache_times[torrent_id][key] = t
- return self.create_status_dict([torrent_id], keys)[torrent_id]
+ try:
+ self.torrents[torrent_id][0] = t
+ self.torrents[torrent_id][1].update(result)
+ for key in keys_to_get:
+ self.cache_times[torrent_id][key] = t
+ return self.create_status_dict([torrent_id], keys)[torrent_id]
+ except KeyError:
+ log.debug(
+ f'Status missing for torrent (removed?): {torrent_id}'
+ )
+ return {}
return d.addCallback(on_status, torrent_id)
else:
diff --git a/deluge/ui/tracker_icons.py b/deluge/ui/tracker_icons.py
index c10cd2f8e..5f619af63 100644
--- a/deluge/ui/tracker_icons.py
+++ b/deluge/ui/tracker_icons.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
#
@@ -7,14 +6,14 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
import os
-from tempfile import mkstemp
+import tempfile
+from html.parser import HTMLParser
+from urllib.parse import urljoin, urlparse
from twisted.internet import defer, threads
-from twisted.web.error import PageRedirect
+from twisted.python.failure import Failure
from twisted.web.resource import ForbiddenResource, NoResource
from deluge.component import Component
@@ -23,12 +22,9 @@ from deluge.decorators import proxy
from deluge.httpdownloader import download_file
try:
- from html.parser import HTMLParser
- from urllib.parse import urljoin, urlparse
+ import chardet
except ImportError:
- # PY2 fallback
- from HTMLParser import HTMLParser
- from urlparse import urljoin, urlparse # pylint: disable=ungrouped-imports
+ chardet = None
try:
from PIL import Image
@@ -38,7 +34,7 @@ except ImportError:
log = logging.getLogger(__name__)
-class TrackerIcon(object):
+class TrackerIcon:
"""
Represents a tracker's icon
"""
@@ -207,17 +203,19 @@ class TrackerIcons(Component):
else:
# We need to fetch it
self.pending[host] = []
+ tmp_file = tempfile.mkstemp(prefix='deluge_trackericon_html.')
+ filename = tmp_file[1]
# Start callback chain
- d = self.download_page(host)
+ d = self.download_page(host, filename)
d.addCallbacks(
self.on_download_page_complete,
self.on_download_page_fail,
- errbackArgs=(host,),
)
d.addCallback(self.parse_html_page)
d.addCallbacks(
self.on_parse_complete, self.on_parse_fail, callbackArgs=(host,)
)
+ d.addBoth(self.del_tmp_file, tmp_file)
d.addCallback(self.download_icon, host)
d.addCallbacks(
self.on_download_icon_complete,
@@ -229,24 +227,38 @@ class TrackerIcons(Component):
d.addCallback(self.store_icon, host)
return d
- def download_page(self, host, url=None):
- """
- Downloads a tracker host's page
+ @staticmethod
+ def del_tmp_file(result, tmp_file):
+ """Remove tmp_file created when downloading tracker page"""
+ fd, filename = tmp_file
+ try:
+ os.close(fd)
+ os.remove(filename)
+ except OSError:
+ log.debug(f'Unable to delete temporary file: {filename}')
+
+ return result
+
+ def download_page(
+ self, host: str, filename: str, url: str = None
+ ) -> 'defer.Deferred[str]':
+ """Downloads a tracker host's page
+
If no url is provided, it bases the url on the host
- :param host: the tracker host
- :type host: string
- :param url: the (optional) url of the host
- :type url: string
- :returns: the filename of the tracker host's page
- :rtype: Deferred
+ Args:
+ host: The tracker host
+ filename: Location to download page
+ url: The url of the host
+
+ Returns:
+ The filename of the tracker host's page
"""
if not url:
url = self.host_to_url(host)
- log.debug('Downloading %s %s', host, url)
- tmp_fd, tmp_file = mkstemp(prefix='deluge_ticon.')
- os.close(tmp_fd)
- return download_file(url, tmp_file, force_filename=True, handle_redirects=False)
+
+ log.debug(f'Downloading {host} {url} to {filename}')
+ return download_file(url, filename, force_filename=True)
def on_download_page_complete(self, page):
"""
@@ -260,33 +272,18 @@ class TrackerIcons(Component):
log.debug('Finished downloading %s', page)
return page
- def on_download_page_fail(self, f, host):
- """
- Recovers from download error
+ def on_download_page_fail(self, failure: 'Failure') -> 'Failure':
+ """Runs any download failure clean-up functions
- :param f: the failure that occurred
- :type f: Failure
- :param host: the name of the host whose page failed to download
- :type host: string
- :returns: a Deferred if recovery was possible
- else the original failure
- :rtype: Deferred or Failure
- """
- error_msg = f.getErrorMessage()
- log.debug('Error downloading page: %s', error_msg)
- d = f
- if f.check(PageRedirect):
- # Handle redirect errors
- location = urljoin(self.host_to_url(host), error_msg.split(' to ')[1])
- self.redirects[host] = url_to_host(location)
- d = self.download_page(host, url=location)
- d.addCallbacks(
- self.on_download_page_complete,
- self.on_download_page_fail,
- errbackArgs=(host,),
- )
+ Args:
+ failure: The failure that occurred.
- return d
+ Returns:
+ The original failure.
+
+ """
+ log.debug(f'Error downloading page: {failure.getErrorMessage()}')
+ return failure
@proxy(threads.deferToThread)
def parse_html_page(self, page):
@@ -298,17 +295,19 @@ class TrackerIcons(Component):
:returns: a Deferred which callbacks a list of available favicons (url, type)
:rtype: Deferred
"""
- with open(page, 'r') as _file:
+ encoding = 'UTF-8'
+ if chardet:
+ with open(page, 'rb') as _file:
+ result = chardet.detect(_file.read())
+ encoding = result['encoding']
+
+ with open(page, encoding=encoding) as _file:
parser = FaviconParser()
for line in _file:
parser.feed(line)
if parser.left_head:
break
parser.close()
- try:
- os.remove(page)
- except OSError as ex:
- log.warning('Could not remove temp file: %s', ex)
return parser.get_icons()
@@ -382,7 +381,7 @@ class TrackerIcons(Component):
try:
with Image.open(icon_name):
pass
- except IOError as ex:
+ except OSError as ex:
raise InvalidIconError(ex)
else:
if not os.path.getsize(icon_name):
@@ -423,22 +422,7 @@ class TrackerIcons(Component):
error_msg = f.getErrorMessage()
log.debug('Error downloading icon from %s: %s', host, error_msg)
d = f
- if f.check(PageRedirect):
- # Handle redirect errors
- location = urljoin(self.host_to_url(host), error_msg.split(' to ')[1])
- d = self.download_icon(
- [(location, extension_to_mimetype(location.rpartition('.')[2]))]
- + icons,
- host,
- )
- if not icons:
- d.addCallbacks(
- self.on_download_icon_complete,
- self.on_download_icon_fail,
- callbackArgs=(host,),
- errbackArgs=(host,),
- )
- elif f.check(NoResource, ForbiddenResource) and icons:
+ if f.check(NoResource, ForbiddenResource) and icons:
d = self.download_icon(icons, host)
elif f.check(NoIconsError):
# No icons, try favicon.ico as an act of desperation
@@ -477,14 +461,17 @@ class TrackerIcons(Component):
# Requires Pillow(PIL) to resize.
if icon and Image:
filename = icon.get_filename()
+ remove_old = False
with Image.open(filename) as img:
if img.size > (16, 16):
new_filename = filename.rpartition('.')[0] + '.png'
img = img.resize((16, 16), Image.ANTIALIAS)
img.save(new_filename)
if new_filename != filename:
- os.remove(filename)
- icon = TrackerIcon(new_filename)
+ remove_old = True
+ if remove_old:
+ os.remove(filename)
+ icon = TrackerIcon(new_filename)
return icon
def store_icon(self, icon, host):
@@ -617,11 +604,13 @@ MIME_MAP = {
'image/png': 'png',
'image/vnd.microsoft.icon': 'ico',
'image/x-icon': 'ico',
+ 'image/svg+xml': 'svg',
'gif': 'image/gif',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'png': 'image/png',
'ico': 'image/vnd.microsoft.icon',
+ 'svg': 'image/svg+xml',
}
diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py
index 0986ec777..338f8a8e0 100644
--- a/deluge/ui/ui.py
+++ b/deluge/ui/ui.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
@@ -7,9 +6,8 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import logging
+import sys
import deluge.common
import deluge.configmanager
@@ -27,7 +25,7 @@ except ImportError:
return
-class UI(object):
+class UI:
"""
Base class for UI implementations.
@@ -60,7 +58,7 @@ class UI(object):
return self.__options
def start(self, parser=None):
- args = deluge.common.unicode_argv()[1:]
+ args = sys.argv[1:]
if parser is None:
parser = self.parser
self.__options = self.parse_args(parser, args)
diff --git a/deluge/ui/ui_entry.py b/deluge/ui/ui_entry.py
index 71ce83783..e185fda33 100644
--- a/deluge/ui/ui_entry.py
+++ b/deluge/ui/ui_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>
@@ -12,8 +11,6 @@
# user runs the command 'deluge'.
"""Main starting point for Deluge"""
-from __future__ import unicode_literals
-
import argparse
import logging
import os
@@ -100,7 +97,7 @@ def start_ui():
# If the UI is set as default, indicate this in help by prefixing with a star.
subactions = subparsers._get_subactions()
prefix = '*' if ui == default_ui else ' '
- subactions[-1].metavar = '%s %s' % (prefix, ui)
+ subactions[-1].metavar = f'{prefix} {ui}'
# Insert a default UI subcommand unless one of the ambiguous_args are specified
parser.set_default_subparser(default_ui, abort_opts=AMBIGUOUS_CMD_ARGS)
@@ -115,7 +112,7 @@ def start_ui():
try:
ui = ui_entrypoints[selected_ui](
- prog='%s %s' % (os.path.basename(sys.argv[0]), selected_ui), ui_args=ui_args
+ prog=f'{os.path.basename(sys.argv[0])} {selected_ui}', ui_args=ui_args
)
except KeyError:
log.error(
diff --git a/deluge/ui/web/__init__.py b/deluge/ui/web/__init__.py
index 0be7eedb9..3757e0b1c 100644
--- a/deluge/ui/web/__init__.py
+++ b/deluge/ui/web/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
from deluge.ui.web.web import Web
diff --git a/deluge/ui/web/auth.py b/deluge/ui/web/auth.py
index d631f9186..eacbbf526 100644
--- a/deluge/ui/web/auth.py
+++ b/deluge/ui/web/auth.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 hashlib
import logging
import os
@@ -64,7 +61,7 @@ class Auth(JSONComponent):
"""
def __init__(self, config):
- super(Auth, self).__init__('Auth')
+ super().__init__('Auth')
self.worker = LoopingCall(self._clean_sessions)
self.config = config
diff --git a/deluge/ui/web/common.py b/deluge/ui/web/common.py
index 475f33565..32c29c8c0 100644
--- a/deluge/ui/web/common.py
+++ b/deluge/ui/web/common.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
@@ -7,19 +6,15 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import gettext
from mako.template import Template as MakoTemplate
-from deluge.common import PY2, get_version
+from deluge.common import get_version
def _(text):
text_local = gettext.gettext(text)
- if PY2:
- return text_local.decode('utf-8')
return text_local
diff --git a/deluge/ui/web/css/deluge.css b/deluge/ui/web/css/deluge.css
index c026b6d8e..946028639 100644
--- a/deluge/ui/web/css/deluge.css
+++ b/deluge/ui/web/css/deluge.css
@@ -6,6 +6,8 @@ body {
border: 0 none;
overflow: hidden;
height: 100%;
+ color: black;
+ background: white;
}
input {
diff --git a/deluge/ui/web/js/deluge-all/AboutWindow.js b/deluge/ui/web/js/deluge-all/AboutWindow.js
index 15fede9b8..cfae7a862 100644
--- a/deluge/ui/web/js/deluge-all/AboutWindow.js
+++ b/deluge/ui/web/js/deluge-all/AboutWindow.js
@@ -104,8 +104,7 @@ Deluge.about.AboutWindow = Ext.extend(Ext.Window, {
{
xtype: 'label',
style: 'padding-top: 5px; font-size: 12px;',
- html:
- '<a href="https://deluge-torrent.org" target="_blank">deluge-torrent.org</a>',
+ html: '<a href="https://deluge-torrent.org" target="_blank">deluge-torrent.org</a>',
},
]);
this.addButton(_('Close'), this.onCloseClick, this);
diff --git a/deluge/ui/web/js/deluge-all/AddTrackerWindow.js b/deluge/ui/web/js/deluge-all/AddTrackerWindow.js
index 8fbe0b221..aaf4a3ff9 100644
--- a/deluge/ui/web/js/deluge-all/AddTrackerWindow.js
+++ b/deluge/ui/web/js/deluge-all/AddTrackerWindow.js
@@ -10,7 +10,8 @@
Ext.ns('Deluge');
// Custom VType validator for tracker urls
-var trackerUrlTest = /(((^https?)|(^udp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
+var trackerUrlTest =
+ /(((^https?)|(^udp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
Ext.apply(Ext.form.VTypes, {
trackerUrl: function (val, field) {
return trackerUrlTest.test(val);
diff --git a/deluge/ui/web/js/deluge-all/Deluge.js b/deluge/ui/web/js/deluge-all/Deluge.js
index 86cae6d89..260ad978f 100644
--- a/deluge/ui/web/js/deluge-all/Deluge.js
+++ b/deluge/ui/web/js/deluge-all/Deluge.js
@@ -25,11 +25,6 @@ Ext.state.Manager.setProvider(
// Add some additional functions to ext and setup some of the
// configurable parameters
Ext.apply(Ext, {
- escapeHTML: function (text) {
- text = String(text).replace('<', '&lt;').replace('>', '&gt;');
- return text.replace('&', '&amp;');
- },
-
isObjectEmpty: function (obj) {
for (var i in obj) {
return false;
diff --git a/deluge/ui/web/js/deluge-all/EditTrackersWindow.js b/deluge/ui/web/js/deluge-all/EditTrackersWindow.js
index f6733aaa6..178fd583f 100644
--- a/deluge/ui/web/js/deluge-all/EditTrackersWindow.js
+++ b/deluge/ui/web/js/deluge-all/EditTrackersWindow.js
@@ -57,6 +57,7 @@ Deluge.EditTrackersWindow = Ext.extend(Ext.Window, {
header: _('Tracker'),
width: 0.9,
dataIndex: 'url',
+ tpl: new Ext.XTemplate('{url:htmlEncode}'),
},
],
columnSort: {
diff --git a/deluge/ui/web/js/deluge-all/FilterPanel.js b/deluge/ui/web/js/deluge-all/FilterPanel.js
index b6e5ec5ca..f1fade120 100644
--- a/deluge/ui/web/js/deluge-all/FilterPanel.js
+++ b/deluge/ui/web/js/deluge-all/FilterPanel.js
@@ -171,5 +171,5 @@ Deluge.FilterPanel.templates = {
tracker_host:
'<div class="x-deluge-filter" style="background-image: url(' +
deluge.config.base +
- 'tracker/{filter});">{filter} ({count})</div>',
+ 'tracker/{filter});">{filter:htmlEncode} ({count})</div>',
};
diff --git a/deluge/ui/web/js/deluge-all/Formatters.js b/deluge/ui/web/js/deluge-all/Formatters.js
index 443bfdf56..6b09abee5 100644
--- a/deluge/ui/web/js/deluge-all/Formatters.js
+++ b/deluge/ui/web/js/deluge-all/Formatters.js
@@ -15,7 +15,23 @@
* @version 1.3
* @singleton
*/
-Deluge.Formatters = {
+Deluge.Formatters = (function () {
+ var charToEntity = {
+ '&': '&amp;',
+ '>': '&gt;',
+ '<': '&lt;',
+ '"': '&quot;',
+ "'": '&#39;',
+ };
+
+ var charToEntityRegex = new RegExp(
+ '(' + Object.keys(charToEntity).join('|') + ')',
+ 'g'
+ );
+ var htmlEncodeReplaceFn = function (match, capture) {
+ return charToEntity[capture];
+ };
+
/**
* Formats a date string in the date representation of the current locale,
* based on the systems timezone.
@@ -24,154 +40,162 @@ Deluge.Formatters = {
* @return {String} a string in the date representation of the current locale
* or "" if seconds < 0.
*/
- date: function (timestamp) {
- function zeroPad(num, count) {
- var numZeropad = num + '';
- while (numZeropad.length < count) {
- numZeropad = '0' + numZeropad;
+ return (Formatters = {
+ date: function (timestamp) {
+ function zeroPad(num, count) {
+ var numZeropad = num + '';
+ while (numZeropad.length < count) {
+ numZeropad = '0' + numZeropad;
+ }
+ return numZeropad;
+ }
+ timestamp = timestamp * 1000;
+ var date = new Date(timestamp);
+ return String.format(
+ '{0}/{1}/{2} {3}:{4}:{5}',
+ zeroPad(date.getDate(), 2),
+ zeroPad(date.getMonth() + 1, 2),
+ date.getFullYear(),
+ zeroPad(date.getHours(), 2),
+ zeroPad(date.getMinutes(), 2),
+ zeroPad(date.getSeconds(), 2)
+ );
+ },
+
+ /**
+ * Formats the bytes value into a string with KiB, MiB or GiB units.
+ *
+ * @param {Number} bytes the filesize in bytes
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with KiB, MiB or GiB units.
+ */
+ size: function (bytes, showZero) {
+ if (!bytes && !showZero) return '';
+ bytes = bytes / 1024.0;
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' KiB';
+ } else {
+ bytes = bytes / 1024;
}
- return numZeropad;
- }
- timestamp = timestamp * 1000;
- var date = new Date(timestamp);
- return String.format(
- '{0}/{1}/{2} {3}:{4}:{5}',
- zeroPad(date.getDate(), 2),
- zeroPad(date.getMonth() + 1, 2),
- date.getFullYear(),
- zeroPad(date.getHours(), 2),
- zeroPad(date.getMinutes(), 2),
- zeroPad(date.getSeconds(), 2)
- );
- },
-
- /**
- * Formats the bytes value into a string with KiB, MiB or GiB units.
- *
- * @param {Number} bytes the filesize in bytes
- * @param {Boolean} showZero pass in true to displays 0 values
- * @return {String} formatted string with KiB, MiB or GiB units.
- */
- size: function (bytes, showZero) {
- if (!bytes && !showZero) return '';
- bytes = bytes / 1024.0;
-
- if (bytes < 1024) {
- return bytes.toFixed(1) + ' KiB';
- } else {
- bytes = bytes / 1024;
- }
-
- if (bytes < 1024) {
- return bytes.toFixed(1) + ' MiB';
- } else {
- bytes = bytes / 1024;
- }
-
- return bytes.toFixed(1) + ' GiB';
- },
-
- /**
- * Formats the bytes value into a string with K, M or G units.
- *
- * @param {Number} bytes the filesize in bytes
- * @param {Boolean} showZero pass in true to displays 0 values
- * @return {String} formatted string with K, M or G units.
- */
- sizeShort: function (bytes, showZero) {
- if (!bytes && !showZero) return '';
- bytes = bytes / 1024.0;
- if (bytes < 1024) {
- return bytes.toFixed(1) + ' K';
- } else {
- bytes = bytes / 1024;
- }
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' MiB';
+ } else {
+ bytes = bytes / 1024;
+ }
- if (bytes < 1024) {
- return bytes.toFixed(1) + ' M';
- } else {
- bytes = bytes / 1024;
- }
+ return bytes.toFixed(1) + ' GiB';
+ },
+
+ /**
+ * Formats the bytes value into a string with K, M or G units.
+ *
+ * @param {Number} bytes the filesize in bytes
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with K, M or G units.
+ */
+ sizeShort: function (bytes, showZero) {
+ if (!bytes && !showZero) return '';
+ bytes = bytes / 1024.0;
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' K';
+ } else {
+ bytes = bytes / 1024;
+ }
- return bytes.toFixed(1) + ' G';
- },
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' M';
+ } else {
+ bytes = bytes / 1024;
+ }
- /**
- * Formats a string to display a transfer speed utilizing {@link #size}
- *
- * @param {Number} bytes the number of bytes per second
- * @param {Boolean} showZero pass in true to displays 0 values
- * @return {String} formatted string with KiB, MiB or GiB units.
- */
- speed: function (bytes, showZero) {
- return !bytes && !showZero ? '' : fsize(bytes, showZero) + '/s';
- },
+ return bytes.toFixed(1) + ' G';
+ },
+
+ /**
+ * Formats a string to display a transfer speed utilizing {@link #size}
+ *
+ * @param {Number} bytes the number of bytes per second
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with KiB, MiB or GiB units.
+ */
+ speed: function (bytes, showZero) {
+ return !bytes && !showZero ? '' : fsize(bytes, showZero) + '/s';
+ },
+
+ /**
+ * Formats a string to show time in a human readable form.
+ *
+ * @param {Number} time the number of seconds
+ * @return {String} a formatted time string. will return '' if seconds == 0
+ */
+ timeRemaining: function (time) {
+ if (time <= 0) {
+ return '&infin;';
+ }
+ time = time.toFixed(0);
+ if (time < 60) {
+ return time + 's';
+ } else {
+ time = time / 60;
+ }
- /**
- * Formats a string to show time in a human readable form.
- *
- * @param {Number} time the number of seconds
- * @return {String} a formatted time string. will return '' if seconds == 0
- */
- timeRemaining: function (time) {
- if (time <= 0) {
- return '&infin;';
- }
- time = time.toFixed(0);
- if (time < 60) {
- return time + 's';
- } else {
- time = time / 60;
- }
-
- if (time < 60) {
- var minutes = Math.floor(time);
- var seconds = Math.round(60 * (time - minutes));
- if (seconds > 0) {
- return minutes + 'm ' + seconds + 's';
+ if (time < 60) {
+ var minutes = Math.floor(time);
+ var seconds = Math.round(60 * (time - minutes));
+ if (seconds > 0) {
+ return minutes + 'm ' + seconds + 's';
+ } else {
+ return minutes + 'm';
+ }
} else {
- return minutes + 'm';
+ time = time / 60;
}
- } else {
- time = time / 60;
- }
-
- if (time < 24) {
- var hours = Math.floor(time);
- var minutes = Math.round(60 * (time - hours));
- if (minutes > 0) {
- return hours + 'h ' + minutes + 'm';
+
+ if (time < 24) {
+ var hours = Math.floor(time);
+ var minutes = Math.round(60 * (time - hours));
+ if (minutes > 0) {
+ return hours + 'h ' + minutes + 'm';
+ } else {
+ return hours + 'h';
+ }
} else {
- return hours + 'h';
+ time = time / 24;
}
- } else {
- time = time / 24;
- }
-
- var days = Math.floor(time);
- var hours = Math.round(24 * (time - days));
- if (hours > 0) {
- return days + 'd ' + hours + 'h';
- } else {
- return days + 'd';
- }
- },
- /**
- * Simply returns the value untouched, for when no formatting is required.
- *
- * @param {Mixed} value the value to be displayed
- * @return the untouched value.
- */
- plain: function (value) {
- return value;
- },
-
- cssClassEscape: function (value) {
- return value.toLowerCase().replace('.', '_');
- },
-};
+ var days = Math.floor(time);
+ var hours = Math.round(24 * (time - days));
+ if (hours > 0) {
+ return days + 'd ' + hours + 'h';
+ } else {
+ return days + 'd';
+ }
+ },
+
+ /**
+ * Simply returns the value untouched, for when no formatting is required.
+ *
+ * @param {Mixed} value the value to be displayed
+ * @return the untouched value.
+ */
+ plain: function (value) {
+ return value;
+ },
+
+ cssClassEscape: function (value) {
+ return value.toLowerCase().replace('.', '_');
+ },
+
+ htmlEncode: function (value) {
+ return !value
+ ? value
+ : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn);
+ },
+ });
+})();
var fsize = Deluge.Formatters.size;
var fsize_short = Deluge.Formatters.sizeShort;
var fspeed = Deluge.Formatters.speed;
@@ -179,3 +203,4 @@ var ftime = Deluge.Formatters.timeRemaining;
var fdate = Deluge.Formatters.date;
var fplain = Deluge.Formatters.plain;
Ext.util.Format.cssClassEscape = Deluge.Formatters.cssClassEscape;
+Ext.util.Format.htmlEncode = Deluge.Formatters.htmlEncode;
diff --git a/deluge/ui/web/js/deluge-all/TorrentGrid.js b/deluge/ui/web/js/deluge-all/TorrentGrid.js
index f664c765c..333d1335c 100644
--- a/deluge/ui/web/js/deluge-all/TorrentGrid.js
+++ b/deluge/ui/web/js/deluge-all/TorrentGrid.js
@@ -17,7 +17,7 @@
return String.format(
'<div class="torrent-name x-deluge-{0}">{1}</div>',
r.data['state'].toLowerCase(),
- value
+ Ext.util.Format.htmlEncode(value)
);
}
function torrentSpeedRenderer(value) {
@@ -62,12 +62,14 @@
'<div style="background: url(' +
deluge.config.base +
'tracker/{0}) no-repeat; padding-left: 20px;">{0}</div>',
- value
+ Ext.util.Format.htmlEncode(value)
);
}
function etaSorter(eta) {
- return eta * -1;
+ if (eta === 0) return Number.MAX_VALUE;
+ if (eta <= -1) return Number.MAX_SAFE_INTEGER;
+ return eta;
}
function dateOrNever(date) {
@@ -75,7 +77,9 @@
}
function timeOrInf(time) {
- return time < 0 ? '&infin;' : ftime(time);
+ if (time === 0) return '';
+ if (time <= -1) return '&infin;';
+ return ftime(time);
}
/**
@@ -320,6 +324,8 @@
{ name: 'ratio', type: 'float' },
{ name: 'distributed_copies', type: 'float' },
{ name: 'time_added', type: 'int' },
+ { name: 'last_seen_complete', type: 'int' },
+ { name: 'completed_time', type: 'int' },
{ name: 'tracker_host' },
{ name: 'download_location' },
{ name: 'total_done', type: 'int' },
diff --git a/deluge/ui/web/js/deluge-all/add/AddWindow.js b/deluge/ui/web/js/deluge-all/add/AddWindow.js
index a4aff067b..f5f2fdf07 100644
--- a/deluge/ui/web/js/deluge-all/add/AddWindow.js
+++ b/deluge/ui/web/js/deluge-all/add/AddWindow.js
@@ -64,20 +64,6 @@ Deluge.add.AddWindow = Ext.extend(Deluge.add.Window, {
this.addButton(_('Cancel'), this.onCancelClick, this);
this.addButton(_('Add'), this.onAddClick, this);
- function torrentRenderer(value, p, r) {
- if (r.data['info_hash']) {
- return String.format(
- '<div class="x-deluge-add-torrent-name">{0}</div>',
- value
- );
- } else {
- return String.format(
- '<div class="x-deluge-add-torrent-name-loading">{0}</div>',
- value
- );
- }
- }
-
this.list = new Ext.list.ListView({
store: new Ext.data.SimpleStore({
fields: [
@@ -91,8 +77,10 @@ Deluge.add.AddWindow = Ext.extend(Deluge.add.Window, {
id: 'torrent',
width: 150,
sortable: true,
- renderer: torrentRenderer,
dataIndex: 'text',
+ tpl: new Ext.XTemplate(
+ '<div class="x-deluge-add-torrent-name">{text:htmlEncode}</div>'
+ ),
},
],
stripeRows: true,
diff --git a/deluge/ui/web/js/deluge-all/add/FilesTab.js b/deluge/ui/web/js/deluge-all/add/FilesTab.js
index fed52282d..d712c023d 100644
--- a/deluge/ui/web/js/deluge-all/add/FilesTab.js
+++ b/deluge/ui/web/js/deluge-all/add/FilesTab.js
@@ -28,6 +28,7 @@ Deluge.add.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
header: _('Filename'),
width: 295,
dataIndex: 'filename',
+ tpl: new Ext.XTemplate('{filename:htmlEncode}'),
},
{
header: _('Size'),
diff --git a/deluge/ui/web/js/deluge-all/add/OptionsPanel.js b/deluge/ui/web/js/deluge-all/add/OptionsPanel.js
index 6b75686a3..365b00190 100644
--- a/deluge/ui/web/js/deluge-all/add/OptionsPanel.js
+++ b/deluge/ui/web/js/deluge-all/add/OptionsPanel.js
@@ -134,9 +134,8 @@ Deluge.add.OptionsPanel = Ext.extend(Ext.TabPanel, {
nodes,
function (node) {
if (node.attributes.fileindex < 0) return;
- var priorities = this.form.optionsManager.get(
- 'file_priorities'
- );
+ var priorities =
+ this.form.optionsManager.get('file_priorities');
priorities[node.attributes.fileindex] = newValue;
this.form.optionsManager.update('file_priorities', priorities);
},
diff --git a/deluge/ui/web/js/deluge-all/details/DetailsTab.js b/deluge/ui/web/js/deluge-all/details/DetailsTab.js
index fdb4f7f0d..f1da178b1 100644
--- a/deluge/ui/web/js/deluge-all/details/DetailsTab.js
+++ b/deluge/ui/web/js/deluge-all/details/DetailsTab.js
@@ -91,7 +91,9 @@ Deluge.details.DetailsTab = Ext.extend(Ext.Panel, {
for (var field in this.fields) {
if (!Ext.isDefined(data[field])) continue; // This is a field we are not responsible for.
if (data[field] == this.oldData[field]) continue;
- this.fields[field].dom.innerHTML = Ext.escapeHTML(data[field]);
+ this.fields[field].dom.innerHTML = Ext.util.Format.htmlEncode(
+ data[field]
+ );
}
this.oldData = data;
},
diff --git a/deluge/ui/web/js/deluge-all/details/FilesTab.js b/deluge/ui/web/js/deluge-all/details/FilesTab.js
index edc388d19..60de832a6 100644
--- a/deluge/ui/web/js/deluge-all/details/FilesTab.js
+++ b/deluge/ui/web/js/deluge-all/details/FilesTab.js
@@ -18,6 +18,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
header: _('Filename'),
width: 330,
dataIndex: 'filename',
+ tpl: new Ext.XTemplate('{filename:htmlEncode}'),
},
{
header: _('Size'),
diff --git a/deluge/ui/web/js/deluge-all/details/PeersTab.js b/deluge/ui/web/js/deluge-all/details/PeersTab.js
index 66d4a4b95..a1919630d 100644
--- a/deluge/ui/web/js/deluge-all/details/PeersTab.js
+++ b/deluge/ui/web/js/deluge-all/details/PeersTab.js
@@ -73,7 +73,7 @@
header: _('Client'),
width: 125,
sortable: true,
- renderer: fplain,
+ renderer: 'htmlEncode',
dataIndex: 'client',
},
{
diff --git a/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js b/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js
index 4c3720198..8c32da501 100644
--- a/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js
+++ b/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js
@@ -117,8 +117,7 @@ Deluge.preferences.Bandwidth = Ext.extend(Ext.form.FormPanel, {
border: false,
title: '',
defaultType: 'checkbox',
- style:
- 'padding-top: 0px; padding-bottom: 5px; margin-top: 0px; margin-bottom: 0px;',
+ style: 'padding-top: 0px; padding-bottom: 5px; margin-top: 0px; margin-bottom: 0px;',
autoHeight: true,
});
om.bind(
diff --git a/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js b/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js
index ed2abdcdc..4cfed016b 100644
--- a/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js
+++ b/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js
@@ -46,7 +46,6 @@ Deluge.preferences.PreferencesWindow = Ext.extend(Ext.Window, {
columns: [
{
id: 'name',
- renderer: fplain,
dataIndex: 'name',
},
],
diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js
index 31eca735c..ee761aa6f 100644
--- a/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js
+++ b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js
@@ -80,9 +80,8 @@ Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, {
// Generate the column configs with the correct width setting
for (var i = 0; i < numCols; i++) {
var cc = Ext.apply({ items: [] }, colCfg);
- cc[
- this.columns[i] <= 1 ? 'columnWidth' : 'width'
- ] = this.columns[i];
+ cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] =
+ this.columns[i];
if (this.defaults) {
cc.defaults = Ext.apply(
cc.defaults || {},
diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py
index ffdee342c..3f256140e 100644
--- a/deluge/ui/web/json_api.py
+++ b/deluge/ui/web/json_api.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Damien Churchill <damoxc@gmail.com>
#
@@ -7,8 +6,7 @@
# See LICENSE for more details.
#
-from __future__ import division, unicode_literals
-
+import cgi
import json
import logging
import os
@@ -16,7 +14,6 @@ import shutil
import tempfile
from base64 import b64encode
from types import FunctionType
-from xml.sax.saxutils import escape as xml_escape
from twisted.internet import defer, reactor
from twisted.internet.defer import Deferred, DeferredList
@@ -39,7 +36,7 @@ log = logging.getLogger(__name__)
class JSONComponent(component.Component):
def __init__(self, name, interval=1, depend=None):
- super(JSONComponent, self).__init__(name, interval, depend)
+ super().__init__(name, interval, depend)
self._json = component.get('JSON')
self._json.register_object(self, name)
@@ -146,7 +143,7 @@ class JSON(resource.Resource, component.Component):
params = request_data['params']
request_id = request_data['id']
except KeyError as ex:
- message = 'Invalid JSON request, missing param %s in %s' % (
+ message = 'Invalid JSON request, missing param {} in {}'.format(
ex,
request_data,
)
@@ -167,7 +164,7 @@ class JSON(resource.Resource, component.Component):
except Exception as ex:
log.error('Error calling method `%s`: %s', method, ex)
log.exception(ex)
- error = {'message': '%s: %s' % (ex.__class__.__name__, str(ex)), 'code': 3}
+ error = {'message': f'{ex.__class__.__name__}: {str(ex)}', 'code': 3}
return request_id, result, error
@@ -184,7 +181,7 @@ class JSON(resource.Resource, component.Component):
"""
log.error(reason)
response['error'] = {
- 'message': '%s: %s' % (reason.__class__.__name__, str(reason)),
+ 'message': f'{reason.__class__.__name__}: {str(reason)}',
'code': 4,
}
return self._send_response(request, response)
@@ -194,7 +191,7 @@ class JSON(resource.Resource, component.Component):
Handler to take the json data as a string and pass it on to the
_handle_request method for further processing.
"""
- content_type = request.getHeader(b'content-type').decode()
+ content_type, _ = cgi.parse_header(request.getHeader(b'content-type').decode())
if content_type != 'application/json':
message = 'Invalid JSON request content-type: %s' % content_type
raise JSONException(message)
@@ -221,7 +218,7 @@ class JSON(resource.Resource, component.Component):
'id': None,
'error': {
'code': 5,
- 'message': '%s: %s' % (reason.__class__.__name__, str(reason)),
+ 'message': f'{reason.__class__.__name__}: {str(reason)}',
},
}
return self._send_response(request, response)
@@ -288,7 +285,7 @@ class JSON(resource.Resource, component.Component):
FILES_KEYS = ['files', 'file_progress', 'file_priorities']
-class EventQueue(object):
+class EventQueue:
"""
This class subscribes to events from the core and stores them until all
the subscribed listeners have received the events.
@@ -378,10 +375,8 @@ class WebApi(JSONComponent):
methods available from the core RPC.
"""
- XSS_VULN_KEYS = ['name', 'message', 'comment', 'tracker_status', 'peers']
-
def __init__(self):
- super(WebApi, self).__init__('Web', depend=['SessionProxy'])
+ super().__init__('Web', depend=['SessionProxy'])
self.hostlist = HostList()
self.core_config = CoreConfig()
self.event_queue = EventQueue()
@@ -518,7 +513,7 @@ class WebApi(JSONComponent):
return d
def got_stats(stats):
- ui_info['stats']['num_connections'] = stats['num_peers']
+ ui_info['stats']['num_connections'] = stats['peer.num_peers_connected']
ui_info['stats']['upload_rate'] = stats['payload_upload_rate']
ui_info['stats']['download_rate'] = stats['payload_download_rate']
ui_info['stats']['download_protocol_rate'] = (
@@ -527,9 +522,9 @@ class WebApi(JSONComponent):
ui_info['stats']['upload_protocol_rate'] = (
stats['upload_rate'] - stats['payload_upload_rate']
)
- ui_info['stats']['dht_nodes'] = stats['dht_nodes']
+ ui_info['stats']['dht_nodes'] = stats['dht.dht_nodes']
ui_info['stats']['has_incoming_connections'] = stats[
- 'has_incoming_connections'
+ 'net.has_incoming_connections'
]
def got_filters(filters):
@@ -555,13 +550,13 @@ class WebApi(JSONComponent):
d3 = client.core.get_session_status(
[
- 'num_peers',
+ 'peer.num_peers_connected',
'payload_download_rate',
'payload_upload_rate',
'download_rate',
'upload_rate',
- 'dht_nodes',
- 'has_incoming_connections',
+ 'dht.dht_nodes',
+ 'net.has_incoming_connections',
]
)
d3.addCallback(got_stats)
@@ -584,7 +579,7 @@ class WebApi(JSONComponent):
paths = []
info = {}
for index, torrent_file in enumerate(files):
- path = xml_escape(torrent_file['path'])
+ path = torrent_file['path']
paths.append(path)
torrent_file['progress'] = file_progress[index]
torrent_file['priority'] = file_priorities[index]
@@ -621,25 +616,10 @@ class WebApi(JSONComponent):
file_tree.walk(walk)
d.callback(file_tree.get_tree())
- def _on_torrent_status(self, torrent, d):
- for key in self.XSS_VULN_KEYS:
- try:
- if key == 'peers':
- for peer in torrent[key]:
- peer['client'] = xml_escape(peer['client'])
- else:
- torrent[key] = xml_escape(torrent[key])
- except KeyError:
- pass
- d.callback(torrent)
-
@export
def get_torrent_status(self, torrent_id, keys):
"""Get the status for a torrent, filtered by status keys."""
- main_deferred = Deferred()
- d = component.get('SessionProxy').get_torrent_status(torrent_id, keys)
- d.addCallback(self._on_torrent_status, main_deferred)
- return main_deferred
+ return component.get('SessionProxy').get_torrent_status(torrent_id, keys)
@export
def get_torrent_files(self, torrent_id):
@@ -1006,7 +986,7 @@ class WebUtils(JSONComponent):
"""
def __init__(self):
- super(WebUtils, self).__init__('WebUtils')
+ super().__init__('WebUtils')
@export
def get_languages(self):
diff --git a/deluge/ui/web/pluginmanager.py b/deluge/ui/web/pluginmanager.py
index 24f20ce94..2da5b6177 100644
--- a/deluge/ui/web/pluginmanager.py
+++ b/deluge/ui/web/pluginmanager.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
import os
@@ -74,22 +71,20 @@ class PluginManager(PluginManagerBase, component.Component):
scripts = component.get('Scripts')
for script in info['scripts']:
- scripts.remove_script(
- '%s/%s' % (name.lower(), os.path.basename(script).lower())
- )
+ scripts.remove_script(f'{name.lower()}/{os.path.basename(script).lower()}')
for script in info['debug_scripts']:
scripts.remove_script(
- '%s/%s' % (name.lower(), os.path.basename(script).lower()), 'debug'
+ f'{name.lower()}/{os.path.basename(script).lower()}', 'debug'
)
scripts.remove_script(
- '%s/%s' % (name.lower(), os.path.basename(script).lower()), 'dev'
+ f'{name.lower()}/{os.path.basename(script).lower()}', 'dev'
)
- super(PluginManager, self).disable_plugin(name)
+ super().disable_plugin(name)
def enable_plugin(self, name):
- super(PluginManager, self).enable_plugin(name)
+ super().enable_plugin(name)
# Get the plugin instance
try:
@@ -105,17 +100,15 @@ class PluginManager(PluginManagerBase, component.Component):
scripts = component.get('Scripts')
for script in info['scripts']:
log.debug('adding script %s for %s', name, os.path.basename(script))
- scripts.add_script(
- '%s/%s' % (name.lower(), os.path.basename(script)), script
- )
+ scripts.add_script(f'{name.lower()}/{os.path.basename(script)}', script)
for script in info['debug_scripts']:
log.debug('adding debug script %s for %s', name, os.path.basename(script))
scripts.add_script(
- '%s/%s' % (name.lower(), os.path.basename(script)), script, 'debug'
+ f'{name.lower()}/{os.path.basename(script)}', script, 'debug'
)
scripts.add_script(
- '%s/%s' % (name.lower(), os.path.basename(script)), script, 'dev'
+ f'{name.lower()}/{os.path.basename(script)}', script, 'dev'
)
def start(self):
@@ -151,11 +144,10 @@ class PluginManager(PluginManagerBase, component.Component):
info = gather_info(plugin)
info['name'] = name
info['scripts'] = [
- 'js/%s/%s' % (name.lower(), os.path.basename(s)) for s in info['scripts']
+ f'js/{name.lower()}/{os.path.basename(s)}' for s in info['scripts']
]
info['debug_scripts'] = [
- 'js/%s/%s' % (name.lower(), os.path.basename(s))
- for s in info['debug_scripts']
+ f'js/{name.lower()}/{os.path.basename(s)}' for s in info['debug_scripts']
]
del info['script_directories']
return info
diff --git a/deluge/ui/web/server.py b/deluge/ui/web/server.py
index ea2658071..f391a78d2 100644
--- a/deluge/ui/web/server.py
+++ b/deluge/ui/web/server.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2010 Damien Churchill <damoxc@gmail.com>
#
@@ -7,8 +6,6 @@
# See LICENSE for more details.
#
-from __future__ import unicode_literals
-
import fnmatch
import json
import logging
@@ -23,8 +20,7 @@ from twisted.web.resource import EncodingResourceWrapper
from deluge import common, component, configmanager
from deluge.common import is_ipv6
-from deluge.core.rpcserver import check_ssl_keys
-from deluge.crypto_utils import get_context_factory
+from deluge.crypto_utils import check_ssl_keys, get_context_factory
from deluge.i18n import set_language, setup_translation
from deluge.ui.tracker_icons import TrackerIcons
from deluge.ui.web.auth import Auth
@@ -376,7 +372,7 @@ class ScriptResource(resource.Resource, component.Component):
order_file = os.path.join(root, '.order')
if os.path.isfile(order_file):
- with open(order_file, 'r') as _file:
+ with open(order_file) as _file:
for line in _file:
if line.startswith('+ '):
order_filename = line.split()[1]
diff --git a/deluge/ui/web/web.py b/deluge/ui/web/web.py
index 4d0624791..f855bd06c 100644
--- a/deluge/ui/web/web.py
+++ b/deluge/ui/web/web.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 print_function, unicode_literals
-
import logging
from twisted.internet.error import CannotListenError
@@ -24,7 +21,7 @@ class Web(UI):
cmd_description = """Web-based user interface (http://localhost:8112)"""
def __init__(self, *args, **kwargs):
- super(Web, self).__init__(
+ super().__init__(
'web', *args, description='Starts the Deluge Web interface', **kwargs
)
self.__server = None
@@ -67,7 +64,7 @@ class Web(UI):
return self.__server
def start(self):
- super(Web, self).start()
+ super().start()
from deluge.ui.web import server
diff --git a/docs/requirements.txt b/docs/requirements.txt
index ff363e0ff..de42439d9 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,5 @@
sphinx==4.*
myst-parser
-sphinx_rtd_theme
-sphinxcontrib-spelling
+sphinx_rtd_theme==1.0.*
+sphinxcontrib-spelling==7.3.0
+sphinx-autodoc-typehints
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f7db27efd..0e4a41914 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Deluge documentation build configuration file
#
@@ -10,11 +9,11 @@
# All configuration values have a default value; values that are commented out
# serve to show the default value.
+import builtins
import os
import sys
from datetime import date
-from six.moves import builtins
from sphinx.ext import apidoc
from sphinx.ext.autodoc import ClassDocumenter, bool_option
@@ -49,6 +48,7 @@ extensions = [
'sphinx.ext.coverage',
'sphinxcontrib.spelling',
'myst_parser',
+ 'sphinx_autodoc_typehints',
]
napoleon_include_init_with_doc = True
@@ -219,45 +219,13 @@ latex_documents = [
# Autodoc section
# ---------------
-class Mock(object):
-
- __all__ = []
-
- def __init__(self, *args, **kwargs):
- pass
-
- def __call__(self, *args, **kwargs):
- return ''
-
- @classmethod
- def __getattr__(cls, name):
- if name in ('__file__', '__path__', 'xdg_config_home'):
- return '/dev/null'
- elif name[0] == name[0].upper():
- mock_type = type(name, (), {})
- mock_type.__module__ = __name__
- return mock_type
- else:
- return Mock()
-
- def __add__(self, other):
- return other
-
- def __or__(self, __):
- return Mock()
-
-
-# Use custom mock as autodoc_mock_imports fails to handle these modules.
-MOCK_MODULES = ['deluge._libtorrent', 'xdg', 'xdg.BaseDirectory']
-
-for mod_name in MOCK_MODULES:
- sys.modules[mod_name] = Mock()
# Must add these for autodoc to import packages successfully
builtins.__dict__['_'] = lambda x: x
builtins.__dict__['_n'] = lambda s, p, n: s if n == 1 else p
autodoc_mock_imports = [
+ 'deluge._libtorrent',
'twisted',
'rencode',
'OpenSSL',
diff --git a/docs/source/contributing/testing.md b/docs/source/contributing/testing.md
index 4df8ca669..beb30a41c 100644
--- a/docs/source/contributing/testing.md
+++ b/docs/source/contributing/testing.md
@@ -1,10 +1,6 @@
# Running tests
-Deluge testing is implemented using Trial which is Twisted's testing framework
-and an extension of Python's unittest.
-
-See Twisted website for documentation on [Twisted Trial](http://twistedmatrix.com/trac/wiki/TwistedTrial)
-and [Writing tests using Trial](http://twistedmatrix.com/documents/current/core/howto/testing.html).
+Testing uses [PyTest] framework and [PyTest-Twisted] to handle Twisted framework.
## Testing
@@ -12,16 +8,6 @@ The tests are located in the source folder under `deluge/tests`.
The tests are run from the project root directory.
View the unit test coverage at: [deluge-torrent.github.io](http://deluge-torrent.github.io)
-### Trial
-
-Here are some examples that show running all the tests through to selecting an
-individual test.
-
- trial deluge
- trial deluge.tests.test_client
- trial deluge.tests.test_client.ClientTestCase
- trial deluge.tests.test_client.ClientTestCase.test_connect_localclient
-
### Pytest
pytest deluge/tests
@@ -41,7 +27,6 @@ All the tests for Deluge can be run using [Tox](https://pypi.python.org/pypi/tox
### See available targets:
tox -l
- py27
py3
lint
docs
@@ -54,11 +39,12 @@ All the tests for Deluge can be run using [Tox](https://pypi.python.org/pypi/tox
tox -e lint
-## Travis CI
+## CI
+
+Deluge develop branch is tested automatically by GitHub actions.
-Deluge develop branch is tested automatically by [Travis].
-When creating a pull request (PR) on [github], Travis will automatically run
-the unit tests with the code in the PR.
+When creating a pull request (PR) on [github], units tests will be automatically be run.
-[travis]: https://travis-ci.org/deluge-torrent/deluge
[github]: https://github.com/deluge-torrent/deluge/pulls
+[pytest]: https://docs.pytest.org/en/
+[pytest-twisted]: https://github.com/pytest-dev/pytest-twisted
diff --git a/docs/source/devguide/how-to/update-1.3-plugin.md b/docs/source/devguide/how-to/update-1.3-plugin.md
index 6374a0c0d..9ce6ae14c 100644
--- a/docs/source/devguide/how-to/update-1.3-plugin.md
+++ b/docs/source/devguide/how-to/update-1.3-plugin.md
@@ -11,7 +11,7 @@ compatible with 2.0 and this guide aims to helps with that process.
### Python version matching
-Ensure your code is both Python 2.7 and Python >=3.5 compatible.
+Ensure your code is Python >=3.6 compatible.
In `1.3-stable` the plugins that were built with a specific version of Python
would only be loaded if the system Python also matched.
@@ -19,19 +19,6 @@ would only be loaded if the system Python also matched.
This has change in Deluge 2.0 and it will load any Python version of plugin
eggs so compatibility is essential for end-users not to encounter issues.
-### Six
-
-Use [six] to assist with compatibility.
-
-[six]: https://pythonhosted.org/six/
-
-### Unicode literals
-
-Add the following to files to ensure strings and bytes separation so there
-are no surprises when running on Python 3.
-
- from __future__ import unicode_literals
-
## GTK 3 addition
In order to support both Deluge 1.3 and 2.0 all existing plugin GTK UI files
diff --git a/docs/source/intro/01-install.md b/docs/source/intro/01-install.md
index 0d49d82ba..15e99456b 100644
--- a/docs/source/intro/01-install.md
+++ b/docs/source/intro/01-install.md
@@ -48,9 +48,9 @@ One-click [**Install**](https://dl.flathub.org/repo/appstream/org.deluge_torrent
## <i class="fa fa-windows"></i> Windows
-Unfortunately no official installer package currently available.
+Download [installer](https://ftp.osuosl.org/pub/deluge/windows/?C=M;O=D)
-See [Alternative Installs](#alternative-installs)
+Availble for Windows 7, 8 & 10 for both 32-bit and 64-bit OSes.
## <i class="fa fa-apple"></i> macOS
@@ -96,15 +96,6 @@ The [development PPA] contains daily builds from the `develop` branch.
sudo add-apt-repository -u ppa:deluge-team/develop
sudo apt install deluge
-### Windows Community
-
-Due to move to GTK3 and Python 3 and problems with pyinstaller there are only community
-created installers available.
-
-Check sticky topics in [Windows Forum] for latest updates.
-
-For reference [issue #3201] is tracking progress on an official installer.
-
### macOS Community
#### Unofficial `.app` packages
@@ -138,7 +129,5 @@ sudo port install deluge
[development ppa]: https://launchpad.net/~deluge-team/+archive/ubuntu/develop/
[stable ppa]: https://launchpad.net/~deluge-team/+archive/ubuntu/stable/
[homebrew]: https://brew.sh/
-[issue #3201]: https://dev.deluge-torrent.org/ticket/3201
-[windows forum]: https://forum.deluge-torrent.org/viewforum.php?f=12
[macos forum]: https://forum.deluge-torrent.org/viewforum.php?f=13
[depends]: ../depends.md
diff --git a/gen_web_gettext.py b/gen_web_gettext.py
index fac509736..80186e938 100755
--- a/gen_web_gettext.py
+++ b/gen_web_gettext.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2009-2012 Damien Churchill <damoxc@gmail.com>
#
@@ -10,8 +9,6 @@
"""Script to parse javascript files for translation strings and generate gettext.js"""
-from __future__ import print_function, unicode_literals
-
import os
import re
@@ -119,4 +116,4 @@ if __name__ == '__main__':
print('Possible missed text for translation markup:')
for text, filenames in missed_markup.iteritems():
for filename_lineno in filenames:
- print('{0:<58} {1}'.format(':'.join(filename_lineno), text))
+ print('{:<58} {}'.format(':'.join(filename_lineno), text))
diff --git a/generate_pot.py b/generate_pot.py
index f5cad5b62..efbdc7824 100755
--- a/generate_pot.py
+++ b/generate_pot.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2013 Calum Lind <calumlind@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
@@ -11,8 +10,6 @@
"""Parses Python and Javascript code for translation strings to create the 'deluge.pot' template for translators"""
-from __future__ import print_function, unicode_literals
-
import os
import re
from datetime import datetime
@@ -91,7 +88,7 @@ with open(INFILES_LIST, 'w') as f:
call(xgettext_cmd + ['--language=Python', '-j'])
# Replace YEAR and PACKAGE in the copyright message
-with open(POT_FILEPATH, 'r') as f:
+with open(POT_FILEPATH) as f:
lines = f.readlines()
with open(POT_FILEPATH, 'w') as f:
for line in lines:
diff --git a/minify_web_js.py b/minify_web_js.py
index a5cd97c59..614794a97 100755
--- a/minify_web_js.py
+++ b/minify_web_js.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Calum Lind <calumlind@gmail.com>
# Copyright (C) 2010 Damien Churchill <damoxc@gmail.com>
@@ -15,19 +14,12 @@ Usage: python minify_web_js.py deluge/ui/web/js/deluge-all
"""
-from __future__ import print_function, unicode_literals
-
import fileinput
import fnmatch
import os
import subprocess
import sys
-
-try:
- from shutil import which
-except ImportError:
- # PY2 Fallback
- from distutils.spawn import find_executable as which
+from shutil import which
closure_cmd = None
for cmd in ['closure-compiler', 'closure']:
@@ -77,7 +69,7 @@ def source_files_list(source_dir):
order_file = os.path.join(root, '.order')
if os.path.isfile(order_file):
- with open(order_file, 'r') as _file:
+ with open(order_file) as _file:
for line in _file:
if line.startswith('+ '):
order_filename = line.split()[1]
@@ -104,7 +96,7 @@ def minify_file(file_debug, file_minified):
return minify_closure(file_debug, file_minified)
elif minify:
with open(file_minified, 'w') as file_out:
- with open(file_debug, 'r') as file_in:
+ with open(file_debug) as file_in:
file_out.write(minify(file_in.read()))
return True
diff --git a/msgfmt.py b/msgfmt.py
index c0e093ab6..0d5367c3b 100755
--- a/msgfmt.py
+++ b/msgfmt.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: iso-8859-1 -*-
# Written by Martin v. Lwis <loewis@informatik.hu-berlin.de>
# Plural forms support added by alexander smishlajev <alex@tycobka.lv>
"""
@@ -25,8 +24,6 @@ Options:
--version
Display version information and exit.
"""
-from __future__ import print_function, unicode_literals
-
import array
import ast
import getopt
@@ -103,10 +100,7 @@ def generate():
0,
0,
) # size and offset of hash table
- if sys.version_info.major == 2:
- output += array.array(b'i', offsets).tostring()
- else:
- output += array.array('i', offsets).tobytes()
+ output += array.array('i', offsets).tobytes()
output += ids.encode('utf8')
output += strs.encode('utf8')
return output
@@ -127,11 +121,9 @@ def make(filename, outfile):
outfile = os.path.splitext(infile)[0] + '.mo'
try:
- import io
-
- with io.open(infile, encoding='utf8') as _file:
+ with open(infile, encoding='utf8') as _file:
lines = _file.readlines()
- except IOError as msg:
+ except OSError as msg:
print(msg, file=sys.stderr)
sys.exit(1)
@@ -181,9 +173,6 @@ def make(filename, outfile):
if not line:
continue
line = ast.literal_eval(line)
- # Python 2 ast.literal_eval returns bytes.
- if isinstance(line, bytes):
- line = line.decode('utf8')
if section == section_id:
msgid += line
elif section == section_str:
@@ -202,7 +191,7 @@ def make(filename, outfile):
try:
with open(outfile, 'wb') as _file:
_file.write(output)
- except IOError as msg:
+ except OSError as msg:
print(msg, file=sys.stderr)
diff --git a/packaging/source/make_release.py b/packaging/source/make_release.py
index e1cee0036..277d1cedc 100755
--- a/packaging/source/make_release.py
+++ b/packaging/source/make_release.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# Copyright 2014 Calum Lind <calumlind@gmail.com>
#
@@ -7,18 +6,11 @@
# 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 sys
from hashlib import sha256
from subprocess import call, check_output
-PY2 = sys.version_info.major == 2
-
sdist_formats = 'xztar'
-if PY2:
- sdist_formats = 'tar'
version = check_output(['python', 'version.py']).strip().decode()
@@ -53,7 +45,7 @@ else:
# Calculate shasum and add to sha256sums.txt
with open(tarxz_path, 'rb') as _file:
- sha256sum = '%s %s' % (
+ sha256sum = '{} {}'.format(
sha256(_file.read()).hexdigest(),
os.path.basename(tarxz_path),
)
diff --git a/packaging/systemd/deluge-web.service b/packaging/systemd/deluge-web.service
index b74c0ada8..7904db31a 100644
--- a/packaging/systemd/deluge-web.service
+++ b/packaging/systemd/deluge-web.service
@@ -1,7 +1,7 @@
[Unit]
Description=Deluge Bittorrent Client Web Interface
Documentation=man:deluge-web
-After=network-online.target deluged.service
+After=deluged.service
Wants=deluged.service
[Service]
diff --git a/packaging/systemd/user/deluge-web.service b/packaging/systemd/user/deluge-web.service
new file mode 100644
index 000000000..2c3557eb6
--- /dev/null
+++ b/packaging/systemd/user/deluge-web.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Deluge Bittorrent Client Web Interface
+Documentation=man:deluge-web
+After=deluged.service
+Wants=deluged.service
+
+[Service]
+UMask=027
+
+ExecStart=/usr/bin/deluge-web -d
+
+Restart=on-failure
+Slice=background.slice
+
+[Install]
+WantedBy=default.target
diff --git a/packaging/systemd/user/deluged.service b/packaging/systemd/user/deluged.service
new file mode 100644
index 000000000..b026dee7b
--- /dev/null
+++ b/packaging/systemd/user/deluged.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Deluge Bittorrent Client Daemon
+Documentation=man:deluged
+
+[Service]
+UMask=007
+ExecStart=/usr/bin/deluged -d
+Restart=on-failure
+TimeoutStopSec=300
+Slice=background.slice
+
+[Install]
+WantedBy=default.target
diff --git a/packaging/win/README.md b/packaging/win/README.md
new file mode 100644
index 000000000..272792c3f
--- /dev/null
+++ b/packaging/win/README.md
@@ -0,0 +1,34 @@
+= Deluge Installer for Windows =
+
+Instructions for building the Deluge NSIS Installer for Windows Vista/7/8/8.1/10/11.
+
+== Dependencies ==
+
+- Deluge build: https://deluge.readthedocs.io/en/latest/depends.html
+- PyInstaller: https://pypi.org/project/pyinstaller/
+- NSIS: http://nsis.sourceforge.net/Download
+
+== Build Steps ==
+
+1. Build and Install Deluge on Windows.
+2. Run pyinstaller from the deluge\packaging\win directory:
+
+ `pyinstaller --clean delugewin.spec --distpath freeze`
+
+ The result is a PyInstaller version of Deluge in `packaging\win\freeze`.
+
+3. Run the NSIS scripts:
+
+ `python setup_nsis.py`
+
+ 64-bit python:
+
+ `makensis /Darch=x64 deluge-win-installer.nsi`
+
+ 32-bit python:
+
+ `makensis /Darch=x86 deluge-win-installer.nsi`
+
+ Note: If you don't specify arch defaults to trying x64
+
+The result is a standalone installer in the `packaging\win` directory.
diff --git a/packaging/win32/deluge-win32-installer.nsi b/packaging/win/deluge-win-installer.nsi
index cd481335d..333401509 100644
--- a/packaging/win32/deluge-win32-installer.nsi
+++ b/packaging/win/deluge-win-installer.nsi
@@ -9,7 +9,7 @@
#
# Script version; displayed when running the installer
-!define DELUGE_INSTALLER_VERSION "1.0"
+!define DELUGE_INSTALLER_VERSION "2.0"
# Deluge program information
!define PROGRAM_NAME "Deluge"
@@ -21,10 +21,17 @@
!define PROGRAM_WEB_SITE "http://deluge-torrent.org"
!define LICENSE_FILEPATH "..\..\LICENSE"
-# Python files generated with bbfreeze
-!define BUILD_DIR "build-win32"
-!define BBFREEZE_DIR "${BUILD_DIR}\deluge-bbfreeze-${PROGRAM_VERSION}"
+!include FileFunc.nsh
+
+!ifndef arch
+!define INSTALLER_FILENAME "deluge-${PROGRAM_VERSION}-win64-setup.exe"
+!endif
+!If "${arch}" == "x64"
+!define INSTALLER_FILENAME "deluge-${PROGRAM_VERSION}-win64-setup.exe"
+!EndIf
+!If "${arch}" == "x86"
!define INSTALLER_FILENAME "deluge-${PROGRAM_VERSION}-win32-setup.exe"
+!EndIf
# Set default compressor
SetCompressor /FINAL /SOLID lzma
@@ -69,8 +76,6 @@ Var StartMenuFolder
!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
# Run installation
!insertmacro MUI_PAGE_INSTFILES
-# Popup Message if VC Redist missing
-Page Custom VCRedistMessage
# Display 'finished' page
!insertmacro MUI_PAGE_FINISH
# Uninstaller pages
@@ -105,45 +110,6 @@ Function finishpageaction
CreateShortCut "$DESKTOP\Deluge.lnk" "$INSTDIR\deluge.exe"
FunctionEnd
-# Test if Visual Studio Redistributables 2008 SP1 installed and returns -1 if none installed
-Function CheckVCRedist2008
- Push $R0
- ClearErrors
- ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{FF66E9F6-83E7-3A3E-AF14-8DE9A809A6A4}" "Version"
- IfErrors 0 +2
- StrCpy $R0 "-1"
-
- Push $R1
- ClearErrors
- ReadRegDword $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{9BE518E6-ECC6-35A9-88E4-87755C07200F}" "Version"
- IfErrors 0 VSRedistInstalled
- StrCpy $R1 "-1"
-
- StrCmp $R0 "-1" +3 0
- Exch $R0
- Goto VSRedistInstalled
- StrCmp $R1 "-1" +3 0
- Exch $R1
- Goto VSRedistInstalled
- # else
- Push "-1"
- VSRedistInstalled:
-FunctionEnd
-
-Function VCRedistMessage
- Call CheckVCRedist2008
- Pop $R0
- StrCmp $R0 "-1" 0 end
- MessageBox MB_YESNO|MB_ICONEXCLAMATION "Deluge requires an MSVC package to run \
- but the recommended package does not appear to be installed:$\r$\n$\r$\n\
- Microsoft Visual C++ 2008 SP1 Redistributable Package (x86)$\r$\n$\r$\n\
- Would you like to download it now?" /SD IDNO IDYES clickyes
- Goto end
- clickyes:
- ExecShell open "https://www.microsoft.com/en-us/download/details.aspx?id=26368"
- end:
-FunctionEnd
-
# --- Installation sections ---
!define PROGRAM_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PROGRAM_NAME}"
!define PROGRAM_UNINST_ROOT_KEY "HKLM"
@@ -151,8 +117,17 @@ FunctionEnd
BrandingText "${PROGRAM_NAME} Windows Installer v${DELUGE_INSTALLER_VERSION}"
Name "${PROGRAM_NAME} ${PROGRAM_VERSION}"
-OutFile "${BUILD_DIR}\${INSTALLER_FILENAME}"
-InstallDir "$PROGRAMFILES\Deluge"
+OutFile "${INSTALLER_FILENAME}"
+
+!ifndef arch
+InstallDir "$PROGRAMFILES64\Deluge"
+!endif
+!If "${arch}" == "x64"
+InstallDir "$PROGRAMFILES64\Deluge"
+!endIf
+!If "${arch}" == "x86"
+InstallDir "$PROGRAMFILES32\Deluge"
+!endIf
ShowInstDetails show
ShowUnInstDetails show
diff --git a/packaging/win/delugewin.spec b/packaging/win/delugewin.spec
new file mode 100644
index 000000000..b6b557365
--- /dev/null
+++ b/packaging/win/delugewin.spec
@@ -0,0 +1,183 @@
+# -*- mode: python -*-
+import os
+
+from PyInstaller.utils.hooks import (
+ collect_data_files,
+ collect_submodules,
+ copy_metadata,
+)
+
+datas = []
+binaries = []
+hiddenimports = ['pygame', 'ifaddr']
+
+# Collect Meta Data
+datas += copy_metadata('deluge', recursive=True)
+datas += copy_metadata('service-identity', recursive=True)
+
+# Add Deluge Hidden Imports
+hiddenimports += collect_submodules('deluge')
+
+# Add stdlib as Hidden Imports.
+# This is filtered list that excludes some common examples or stuff not useful in
+# plugins (such as tty, mailbox, turtledemo etc.).
+# It is safe to assume that 90% of that list would already be included anyway.
+stdlib = [
+ 'string',
+ 're',
+ 'unicodedata',
+ 'struct',
+ 'codecs',
+ 'datetime',
+ 'zoneinfo',
+ 'calendar',
+ 'collections',
+ 'array',
+ 'weakref',
+ 'types',
+ 'copy',
+ 'enum',
+ 'numbers',
+ 'math',
+ 'cmath',
+ 'decimal',
+ 'fractions',
+ 'random',
+ 'statistics',
+ 'itertools',
+ 'functools',
+ 'operator',
+ 'pathlib',
+ 'fileinput',
+ 'stat',
+ 'tempfile',
+ 'glob',
+ 'fnmatch',
+ 'shutil',
+ 'pickle',
+ 'copyreg',
+ 'shelve',
+ 'marshal',
+ 'dom',
+ 'sqlite3',
+ 'zlib',
+ 'gzip',
+ 'bz2',
+ 'lzma',
+ 'csv',
+ 'hashlib',
+ 'hmac',
+ 'secrets',
+ 'os',
+ 'io',
+ 'time',
+ 'logging',
+ 'platform',
+ 'errno',
+ 'queue',
+ 'socket',
+ 'ssl',
+ 'email',
+ 'json',
+ 'mimetypes',
+ 'base64',
+ 'binhex',
+ 'binascii',
+ 'quopri',
+ 'uu',
+ 'html',
+ 'xml',
+ 'urllib',
+ 'http',
+ 'ftplib',
+ 'smtplib',
+ 'uuid',
+ 'xmlrpc.client',
+ 'ipaddress',
+ 'locale',
+ 'sys',
+]
+for module in stdlib:
+ hiddenimports += collect_submodules(module, filter=lambda name: 'test' not in name)
+
+# Add Hidden Imports for Plugins
+hiddenimports += collect_submodules('twisted', filter=lambda name: 'test' not in name)
+datas += copy_metadata('twisted', recursive=True)
+
+# Copy UI/Plugin and translation files to where pyinstaller expects
+package_data = collect_data_files('deluge')
+datas += package_data
+
+icon = [src for src, dest in package_data if src.endswith('deluge.ico')][0]
+
+# List of executables to produce
+executables = {
+ 'deluge-script.pyw': {'name': 'deluge', 'console': False, 'gtk': True},
+ 'deluge-gtk-script.pyw': {'name': 'deluge-gtk', 'console': False, 'gtk': True},
+ 'deluge-debug-script.py': {'name': 'deluge-debug', 'console': True, 'gtk': True},
+ 'deluge-console-script.py': {
+ 'name': 'deluge-console',
+ 'console': True,
+ 'gtk': False,
+ },
+ 'deluged-script.pyw': {'name': 'deluged', 'console': False, 'gtk': False},
+ 'deluged-debug-script.py': {'name': 'deluged-debug', 'console': True, 'gtk': False},
+ 'deluge-web-debug-script.py': {
+ 'name': 'deluge-web-debug',
+ 'console': True,
+ 'gtk': False,
+ },
+ 'deluge-web-script.pyw': {'name': 'deluge-web', 'console': False, 'gtk': False},
+}
+
+analysis = {}
+exe = {}
+coll = []
+
+# Perform analysis
+for e, d in executables.items():
+ runtime_hooks = []
+ if d['gtk']:
+ runtime_hooks += [os.path.join(SPECPATH, 'pyi_rth_gtk_csd.py')]
+
+ analysis[e] = Analysis(
+ [os.path.abspath(os.path.join(HOMEPATH, os.pardir, os.pardir, 'Scripts', e))],
+ pathex=[],
+ binaries=binaries,
+ datas=datas,
+ hiddenimports=hiddenimports,
+ hookspath=[],
+ hooksconfig={},
+ runtime_hooks=runtime_hooks,
+ excludes=[],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=None,
+ noarchive=False,
+ )
+
+# Executable
+for e, d in executables.items():
+ exe[e] = EXE(
+ PYZ(analysis[e].pure, analysis[e].zipped_data, cipher=None),
+ analysis[e].scripts,
+ [],
+ exclude_binaries=True,
+ name=d['name'],
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ icon=icon,
+ console=d['console'],
+ disable_windowed_traceback=False,
+ target_arch=None,
+ codesign_identity=None,
+ entitlements_file=None,
+ )
+
+# Collect
+for e, d in executables.items():
+ coll += exe[e], analysis[e].binaries, analysis[e].zipfiles, analysis[e].datas
+
+COLLECT(*coll, strip=False, upx=True, upx_exclude=[], name='Deluge')
diff --git a/packaging/win32/installer-side.bmp b/packaging/win/installer-side.bmp
index 58c2bed5b..58c2bed5b 100644
--- a/packaging/win32/installer-side.bmp
+++ b/packaging/win/installer-side.bmp
Binary files differ
diff --git a/packaging/win32/installer-top.bmp b/packaging/win/installer-top.bmp
index 1e1a94cd7..1e1a94cd7 100644
--- a/packaging/win32/installer-top.bmp
+++ b/packaging/win/installer-top.bmp
Binary files differ
diff --git a/packaging/win/pyi_rth_gtk_csd.py b/packaging/win/pyi_rth_gtk_csd.py
new file mode 100644
index 000000000..345122dd6
--- /dev/null
+++ b/packaging/win/pyi_rth_gtk_csd.py
@@ -0,0 +1,3 @@
+import os
+
+os.environ['GTK_CSD'] = os.getenv('GTK_CSD', '0')
diff --git a/packaging/win/setup_nsis.py b/packaging/win/setup_nsis.py
new file mode 100644
index 000000000..f34a941e1
--- /dev/null
+++ b/packaging/win/setup_nsis.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2012-2015 Calum Lind <calumlind@gmail.com>
+# Copyright (C) 2010 Damien Churchill <damoxc@gmail.com>
+# Copyright (C) 2009-2010 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2009 Jesper Lund <mail@jesperlund.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+import os
+
+import deluge.common
+
+# Get build_version from installed deluge.
+build_version = deluge.common.get_version()
+build_dir = os.path.join('freeze', 'Deluge')
+
+# Copy version info to file for nsis script.
+with open('VERSION.tmp', 'w') as ver_file:
+ ver_file.write('build_version = "%s"' % build_version)
+
+# Create the install and uninstall file list for NSIS.
+filedir_list = []
+for root, dirnames, filenames in os.walk(build_dir):
+ dirnames.sort()
+ filenames.sort()
+ filedir_list.append((root[len(build_dir) :], filenames))
+
+with open('install_files.nsh', 'w') as f:
+ f.write('; Files to install\n')
+ for dirname, files in filedir_list:
+ if not dirname:
+ dirname = os.sep
+ f.write('\nSetOutPath "$INSTDIR%s"\n' % dirname)
+ for filename in files:
+ f.write('File ' + build_dir + os.path.join(dirname, filename) + '\n')
+
+with open('uninstall_files.nsh', 'w') as f:
+ f.write('; Files to uninstall\n')
+ for dirname, files in reversed(filedir_list):
+ f.write('\n')
+ if not dirname:
+ dirname = os.sep
+ for filename in files:
+ f.write('Delete "$INSTDIR%s"\n' % os.path.join(dirname, filename))
+ f.write('RMDir "$INSTDIR%s"\n' % dirname)
diff --git a/packaging/win32/DelugeStart Theme/etc/gtk-2.0/gtkrc b/packaging/win32/DelugeStart Theme/etc/gtk-2.0/gtkrc
deleted file mode 100644
index 3539a6289..000000000
--- a/packaging/win32/DelugeStart Theme/etc/gtk-2.0/gtkrc
+++ /dev/null
@@ -1,25 +0,0 @@
-gtk-theme-name = "DelugeStart"
-gtk-icon-theme-name = "Tango"
-gtk-fallback-icon-theme = "hicolor"
-gtk-alternative-button-order = 1
-gtk-alternative-sort-arrows = 1
-gtk-auto-mnemonics = 1
-gtk-show-input-method-menu = 0
-gtk-show-unicode-menu = 0
-
-#gtk-toolbar-icon-size = small-toolbar
-gtk-button-images = 0
-gtk-menu-images = 1
-
-style "notebook"
-{
- xthickness = 1
- ythickness = 1
-}
-widget_class "*<GtkNotebook>" style "notebook"
-
-style "user-font"
-{
- font_name="9"
-}
-widget_class "*" style "user-font"
diff --git a/packaging/win32/DelugeStart Theme/lib/gtk-2.0/2.10.0/engines/libmurrine.dll b/packaging/win32/DelugeStart Theme/lib/gtk-2.0/2.10.0/engines/libmurrine.dll
deleted file mode 100644
index 12134fc0a..000000000
--- a/packaging/win32/DelugeStart Theme/lib/gtk-2.0/2.10.0/engines/libmurrine.dll
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check1.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check1.png
deleted file mode 100644
index 8cdd1cb23..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check1.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check2.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check2.png
deleted file mode 100644
index a484a7019..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check2.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check3.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check3.png
deleted file mode 100644
index faafa4748..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check3.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check4.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check4.png
deleted file mode 100644
index bbaa5af19..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check4.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check5.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check5.png
deleted file mode 100644
index e6b81d80c..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/check5.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/checklight.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/checklight.png
deleted file mode 100644
index b00483813..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/checklight.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option1.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option1.png
deleted file mode 100644
index eba2cbf67..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option1.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option2.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option2.png
deleted file mode 100644
index 22779c3d1..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option2.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option3.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option3.png
deleted file mode 100644
index ecbb7e4d2..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option3.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option4.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option4.png
deleted file mode 100644
index 42aed6679..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Check-Radio/option4.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Icons/close.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Icons/close.png
deleted file mode 100644
index d9b0608f2..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Icons/close.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-horiz.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-horiz.png
deleted file mode 100644
index 4c5468fd9..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-horiz.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-vert.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-vert.png
deleted file mode 100644
index 4c5468fd9..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/scroll-thumb-vert.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-insens.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-insens.png
deleted file mode 100644
index 1e60f4bef..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-insens.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-prelight.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-prelight.png
deleted file mode 100644
index db46ba089..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz-prelight.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz.png
deleted file mode 100644
index fe5babaaf..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-horiz.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-insens.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-insens.png
deleted file mode 100644
index ea9bd9999..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-insens.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-prelight.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-prelight.png
deleted file mode 100644
index 35ed7cf75..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert-prelight.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert.png
deleted file mode 100644
index 2ffe36930..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/slider-vert.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-horiz.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-horiz.png
deleted file mode 100644
index 25a99c204..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-horiz.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-vert.png b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-vert.png
deleted file mode 100644
index 094364fa5..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/Scrollbars/trough-scrollbar-vert.png
+++ /dev/null
Binary files differ
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/gtkrc b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/gtkrc
deleted file mode 100644
index de4001742..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/gtkrc
+++ /dev/null
@@ -1,519 +0,0 @@
-# DelugeStart ( based upon ANewStart by alecive )
-# Licensed under the GPL.
-# Requires Murrine v0.91.0
-
-gtk_color_scheme = "bg_color:#F5F5F5\nselected_bg_color:#8DCCF0\nbase_color:#FFFFFF" # Background, base
-gtk_color_scheme = "fg_color:#3C4343\nselected_fg_color:#1E2222\ntext_color:#3C4343" # Foreground, text
-#gtk_color_scheme = "sidebar_color:#DEDEDE" # Custom colors
-gtk_color_scheme = "tooltip_bg_color:#B7DB67\ntooltip_fg_color:#F5F5F5" # Tooltips
-gtk_color_scheme = "theme_color_04:#a9a49c\ntheme_color_26:#292421\ntheme_color_27:#C7C7C7" # Other colors
-#gtk_color_scheme = "link_color:#08C" # Hyperlinks
-
-gtk-icon-sizes = "panel-menu=23,23:panel=21,21:gtk-button=17,17:gtk-large-toolbar=22,22"
-
-gtk-button-images = 0 # Disables icons for buttons with text
-gtk-toolbar-style = 0 # Disables text in toolbar
-gtk-auto-mnemonics = 1 # Disables lines under menu items
-
-style "default"
-{
- xthickness = 1
- ythickness = 1
-
- GtkArrow::arrow-scaling = 0.6
- GtkComboBox::arrow-scaling = 0.2
-
- GtkScrolledWindow ::scrollbar-spacing = 0
- GtkScrolledWindow ::scrollbar-within-bevel = 0
-
- GtkScrollbar::slider_width = 11
- GtkScrollbar::has-backward-stepper = 0
- GtkScrollbar::has-forward-stepper = 0
- GtkScrollbar::min-slider-length = 30
-
- GtkButton::child-displacement-x = 1
- GtkButton::child-displacement-y = 1
- GtkButton::default-border = { 0, 0, 0, 0 }
-
- GtkCheckButton::indicator-size = 12
-
- GtkPaned::handle-size = 6
-
- GtkRange::trough-border = 1
- GtkRange::stepper-size = 12
- GtkRange::trough-under-steppers = 1
- GtkRange::slider-width = 14
-
- GtkScale::slider-length = 14
- GtkScale::slider-width = 14
- GtkScale::trough-side-details = 1
- GtkScale::trough-border = 1
-
- GtkExpander::expander-size = 14
- GtkTreeView::expander-size = 14
- GtkTreeView::indent-expanders = 0
-
- GtkMenu::horizontal-offset = 2
- GtkMenu::vertical-offset = 2
- GtkMenu::horizontal-padding = 2
- GtkMenu::vertical-padding = 2
-
- GtkMenuItem::arrow-spacing = 0
-
- GtkMenuBar::internal-padding = 2
- GtkMenuBar::shadow_type = GTK_SHADOW_NONE
-
- #set to the same as roundness, used for better hotspot selection of tabs
- GtkNotebook::tab-curvature = 3
- GtkNotebook::tab-overlap = -1
-
- GtkToolbar::internal-padding = 2
- GtkToolbar::horizontal-padding = 0
- GtkToolbar::vertical-padding = 0
- GtkToolbar::shadow_type = GTK_SHADOW_NONE #gtk.SHADOW_IN, gtk.SHADOW_OUT, gtk.SHADOW_ETCHED_IN or gtk.SHADOW_ETCHED_OUT
-
- WnckTasklist::fade-overlay-rect = 0
- # The following line hints to gecko (and possibly other appliations)
- # that the entry should be drawn transparently on the canvas.
- # Without this, gecko will fill in the background of the entry.
- # GtkEntry::honors-transparent-bg-hint = 1
-
- GtkEntry::progress-border = { 2, 2, 2, 2 }
-
- fg[NORMAL] = @fg_color
- fg[PRELIGHT] = @fg_color
- fg[SELECTED] = @selected_fg_color
- fg[ACTIVE] = @fg_color
- fg[INSENSITIVE] = darker (@bg_color)
-
- bg[NORMAL] = @bg_color
- bg[PRELIGHT] = shade (1.04, @bg_color)
- bg[SELECTED] = @selected_bg_color
- bg[INSENSITIVE] = @bg_color
- bg[ACTIVE] = shade (0.9, @bg_color)
-
- base[NORMAL] = @base_color
- base[PRELIGHT] = shade (0.95, @bg_color)
- base[ACTIVE] = mix (0.7, @selected_bg_color, @bg_color)
- base[SELECTED] = @selected_bg_color
- base[INSENSITIVE] = @bg_color
-
- text[NORMAL] = @text_color
- text[PRELIGHT] = @text_color
- text[ACTIVE] = @selected_fg_color
- text[SELECTED] = @selected_fg_color
- text[INSENSITIVE] = darker (@bg_color)
-
- engine "murrine"
- {
- animation = TRUE # FALSE = disabled, TRUE = enabled
- arrowstyle = 1
-# border_shades = { 1.0, 1.0} # draw a gradient on the border.
-# border_colors = { "#E6DDD5", "#E6DDD5" }
- colorize_scrollbar = FALSE # FALSE = disabled, TRUE = enabled
- comboboxstyle = 0 # colorize the GtkComboBox below the arrow.
- contrast = .85 # 0.8 for less contrast, more than 1.0 for more contrast on borders
-# focus_color = @selected_bg_color
-# glazestyle = 5 # 0 = flat, 1 = curved, 2 = concave, 3 = top-curved, 4 = beryl
- glazestyle = 4 # 0 = flat, 1 = curved, 2 = concave, 3 = top-curved, 4 = beryl
- glow_shade = 1.15 # sets glow amount for buttons or widgets
- glowstyle = 0 # 0 = top, 1 = bottom, 2 = top and bottom, 3 = center (vertical), 4 = center (horizontal)
- gradient_shades = { 1.13, 1.02, 1.00, 1.02 } # default: {1.1,1.0,1.0,1.1}
- highlight_shade = 1.0 # set highlight amount for buttons or widgets
- lightborder_shade = 1.2 # sets lightborder amount for buttons or widgets
- lightborderstyle = 1 # 0 = lightborder on top side, 1 = lightborder on all sides
- listviewheaderstyle = 1 # 0 = flat, 1 = glassy, 2 = raised
- listviewstyle = 0 # 0 = nothing, 1 = dotted
- menubaritemstyle = 0 # 0 = menuitem look, 1 = button look
- menubarstyle = 0 # 0 = flat, 1 = glassy, 2 = gradient, 3 = striped
- menuitemstyle = 1 # 0 = flat, 1 = glassy, 2 = striped
- menustyle = 0 # 0 = no vertical menu stripe, 1 = display vertical menu stripe
- prelight_shade = 1.20 #to select the shade level used in the scrollbar's slider, GtkComboBox with comboboxstyle = 1 and in the prelight state with gradient_colors.
- progressbarstyle = 0 # 0 = no stripes, 1 = diagonal stripes, 2 = vertical stripes
- reliefstyle = 4 # 0 = flat, 1 = inset, 2 = shadow, = 3 for a gradient on shadow, = 4 for a stronger shadow.
- rgba = FALSE # FALSE = disabled, TRUE = enabled
- roundness = 2 # 0 = squared, 1 = old default, more will increase roundness
- scrollbarstyle = 0 # 0 = nothing, 1 = circles, 2 = handles, 3 = diagonal stripes, 4 = diagonal stripes and handles, 5 = horizontal stripes, 6 = horizontal stripes and handles
- shadow_shades = { 0.8, 2.2 }
- sliderstyle = 0 # 0 = nothing added, 1 = handles
- spinbuttonstyle = 1
- stepperstyle = 1 # 0 = standard, 1 = integrated stepper handles, 2 = squared steppers with a rounded slider
-# textstyle = 1
- trough_shades = {1.1,0.87}
- toolbarstyle = 0 # 0 = flat, 1 = glassy, 2 = gradient
- separatorstyle = 1 # 0 = solid line, 1 = smooth separator
- }
-}
-
-style "wide"
-{
- xthickness = 2
- ythickness = 2
-}
-
-style "wider"
-{
- xthickness = 3
- ythickness = 3
-}
-
-style "dark"
-{
- #bg[NORMAL] = @bg_color
- #bg[SELECTED] = @selected_bg_color
- #bg[PRELIGHT] = @selected_bg_color
- #bg[ACTIVE] = @selected_bg_color
- #bg[INSENSITIVE] = @bg_color
-
-
- #fg[NORMAL] = @bg_color
- #fg[PRELIGHT] = @selected_fg_color
- #fg[SELECTED] = @selected_fg_color
- #fg[ACTIVE] = @selected_fg_color
- #fg[INSENSITIVE] = @selected_fg_color
-
-}
-
-style "button" = "wide"
-{
- engine "murrine"
- {
- roundness = 3
- gradient_shades = { 1.06, 0.95, 1.06, 0.95}
- border_shades = { .7, .6}
- border_colors = { @bg_color, @bg_color }
-
- lightborderstyle = 1
- lightborder_shade = 1.26
-
- shadow_shades = {2.5,2.2}
- reliefstyle = 4
- }
-}
-
-style "entry" {
- xthickness = 3
- ythickness = 3
-
- bg[SELECTED] = mix (0.4, @selected_bg_color, @base_color)
- fg[SELECTED] = @text_color
-
- engine "murrine"
- {
- focus_color = @selected_bg_color
- lightborder_shade = 1.06
- glow_shade = 1.9
- }
-}
-
-style "terminal"
-{
- text[NORMAL] = darker(@selected_bg_color)
- base[NORMAL] = @bg_color
-
- TerminalScreen::background-darkness = 0.99
-}
-
-style "toolbar"
-{
- ythickness = 0
- engine "murrine"
- {
- gradient_shades = {1.00,0.95,0.95,0.90}
- toolbarstyle = 1
- }
-}
-
-style "toolbar-toggle" = "toolbar"
-{
- text[NORMAL] = @text_color
- text[PRELIGHT] = @text_color
- text[ACTIVE] = @selected_fg_color
- text[SELECTED] = @selected_fg_color
- text[INSENSITIVE] = darker (@bg_color)
-
- engine "murrine"
- {
- toolbarstyle = 0
- }
-}
-
-style "dark-toolbar" = "dark"
-{
- xthickness = 0
- ythickness = 2
-
- engine "murrine"
- {
- border_shades = {1.2, 1.0} # draw a gradient on the border.
- border_colors = { "#62635E", "#62635E" }
- glowstyle = 0
- gradient_shades = {1.1,1.0,1.0,0.7}
- highlight_shade = 1.0
- lightborder_shade = 1.0
- reliefstyle = 1 # 0 = flat, 1 = inset, 2 = shadow, = 3 for a gradient on shadow, = 4 for a stronger shadow.
- }
-}
-
-style "dark-toolbar-sep" = "dark-toolbar"
-{
- xthickness = 2
-}
-
-style "panel"
-{
- xthickness = 0
- ythickness = 0
-
- bg[NORMAL] = "#E3E3E3" # # Default top/bottom panel background
- bg[PRELIGHT] = "#E3E3E3" # @bg_color # panel prelight
-
- engine "murrine"
- {
- #border_shades = {1.2, 1.0} # draw a gradient on the border.
- #border_colors = { "#2D2416", "#2D2416" }
- roundness = 1
- }
-}
-
-style "panel-button" = "panel"
-{
- engine "murrine" {
- roundness = 1
- border_colors = {"#7C7C7C", "#7C7C7C"}
- border_shades = {1.0, 1.0} # draw a gradient on the border.
- gradient_shades = {1.0,1.0,1.0,1.0}
- }
-}
-
-# Based on the default style so that the colors from the button
-# style are overriden again.
-style "treeview-header" = "default"
-{
- xthickness = 1
- ythickness = 1
-
- bg[NORMAL] = "#F2F1F0"
- bg[PRELIGHT] = shade (1.04, "#F2F1F0")
- bg[ACTIVE] = shade (0.96, "#F2F1F0")
- bg[INSENSITIVE] = "#F2F1F0"
-
- engine "murrine" {
- textstyle = 1
- border_shades = {0.90, 0.78}
- glowstyle = 5
- glazestyle = 1
- contrast = 0.8
- lightborder_shade = 1.16
- textstyle = 1
- glow_shade = 1.0
- }
-}
-
-style "progressbar"
-{
- xthickness = 0
- ythickness = 0
-
-
- bg[ACTIVE] = @bg_color
- fg[PRELIGHT] = @selected_fg_color
-
- engine "murrine" {
- trough_shades = {0.9, 0.98}
- roundness = 2
- lightborderstyle = 1
- lightborder_shade = 1.26
- border_shades = {0.8, 0.8}
- gradient_shades = {0.95, 1.1, 0.95, 1.1}
- #trough_border_shades = {0.9, 0.9}
- }
-}
-
-style "statusbar"
-{
- ythickness = 0
- xthickness = 0
-}
-
-style "comboboxentry"
-{
- ythickness = 3
- xthickness = 3
-
- engine "murrine"
- {
- contrast = .8
- }
-}
-
-style "spinbutton"
-{
-}
-
-style "scale"
-{
- bg[ACTIVE] = @bg_color
- bg[PRELIGHT] = shade(1.1, @bg_color)
- fg[PRELIGHT] = @selected_fg_color
-
- engine "murrine" {
- trough_shades = {0.9, 0.98}
- roundness = 6
- lightborderstyle = 1
- lightborder_shade = 1.26
- border_shades = {0.8, 0.8}
- gradient_shades = {0.95, 1.1, 0.95, 1.1}
- #trough_border_shades = {0.9, 0.9}
- highlight_shade = 1.02
- contrast = 1.1
- reliefstyle = 1
- }
-}
-
-style "frame"
-{
-}
-
-style "frame-title" = "frame"
-{
- fg[NORMAL] = lighter (@fg_color)
-}
-
-style "font"
-{
- font_name="9"
-}
-widget_class "*" style "font"
-
-#########################################
-# Matches
-#########################################
-
-# default style is applied to every widget
-class "GtkWidget" style "default"
-
-# Increase the x/ythickness in some widgets
-class "GtkRange" style "default"
-class "GtkFrame" style "frame"
-class "GtkSeparator" style "wide"
-class "GtkEntry" style "entry"
-class "GtkStatusbar" style "statusbar"
-
-widget_class "*<GtkComboBoxEntry>*" style "comboboxentry"
-widget_class "*<GtkCombo>*" style "comboboxentry"
-
-# Toolbar default: light
-class "*HandleBox" style "toolbar"
-class "GtkToolbar" style "toolbar"
-widget_class "*HandleBox" style "toolbar"
-widget_class "*<GtkToolbar>.*" style "toolbar"
-
-#
-# Toolbar exceptions:
-# Browser-type and viewer-type applications get a dark toolbar.
-# Everything below the toolbar for these apps are the content. This will make
-# a separation on function (toolbar) and content (client area)
-
-# Work around for http://bugzilla.gnome.org/show_bug.cgi?id=382646
-style "text-is-fg-color-workaround"
-{
- text[NORMAL] = @fg_color
- text[PRELIGHT] = mix (0.8, @fg_color, '#ffffff')
- text[SELECTED] = @selected_fg_color
- text[ACTIVE] = @fg_color
- text[INSENSITIVE] = darker (@bg_color)
-}
-
-style "text-is-fg-color-workaround-dark"
-{
- #Make it work with this theme!
- text[NORMAL] = @bg_color
- text[PRELIGHT] = mix (1.0, @bg_color, '#ffffff')
-}
-
-# Workaround style for menus where the text color is used instead of the fg color.
-style "menuitem-text-is-fg-color-workaround" {
- text[NORMAL] = @fg_color
- text[PRELIGHT] = @fg_color
- text[SELECTED] = @selected_fg_color
- text[ACTIVE] = @fg_color
- text[INSENSITIVE] = darker (@bg_color)
-}
-
-# Work around for http://bugzilla.gnome.org/show_bug.cgi?id=382646
-# Note that this work around assumes that the combobox is _not_ in appears-as-list mode.
-widget_class "*.<GtkComboBox>.<GtkCellView>"style "text-is-fg-color-workaround"
-# This is the part of the workaround that fixes the menus
-widget "*.gtk-combobox-popup-menu.*" style "menuitem-text-is-fg-color-workaround"
-
-widget "*fullscreen-toolbar" style "dark-toolbar"
-widget "*fullscreen-toolbar.*" style "dark-toolbar"
-widget "*fullscreen-toolbar*.GtkComboBox.GtkCellView" style "text-is-fg-color-workaround-dark"
-
-class "GtkSpinButton" style "spinbutton"
-class "GtkScale" style "scale"
-class "GtkVScale" style "scale"
-class "GtkHScale" style "scale"
-class "GtkButton" style "button"
-
-# General matching following, the order is choosen so that the right styles override each other
-# eg. progressbar needs to be more important then the menu match.
-
-widget_class "*<GtkFrame>" style "frame"
-widget_class "*.<GtkFrame>.<GtkLabel>" style "frame-title"
-
-widget_class "*<GtkStatusbar>*" style "wider"
-widget_class "*<GtkProgressBar>" style "progressbar"
-
-# Treeview header
-widget_class "*.<GtkTreeView>.<GtkButton>" style "treeview-header"
-widget_class "*.<GtkCTree>.<GtkButton>" style "treeview-header"
-widget_class "*.<GtkList>.<GtkButton>" style "treeview-header"
-widget_class "*.<GtkCList>.<GtkButton>" style "treeview-header"
-
-###################################################
-# Special cases and work arounds
-###################################################
-
-# Work around the usage of GtkLabel inside GtkListItems to display text.
-# This breaks because the label is shown on a background that is based on the
-# base color set.
-style "fg-is-text-color-workaround"
-{
- fg[NORMAL] = @text_color
- fg[PRELIGHT] = @text_color
- fg[ACTIVE] = @selected_fg_color
- fg[SELECTED] = @selected_fg_color
- fg[INSENSITIVE] = darker (@bg_color)
-}
-widget_class "*<GtkListItem>*" style "fg-is-text-color-workaround"
-# The same problem also exists for GtkCList and GtkCTree
-# Only match GtkCList and not the parent widgets, because that would also change the headers.
-widget_class "*<GtkCList>" style "fg-is-text-color-workaround"
-
-style "dialog" = "dark"
-{
- bg[NORMAL] = mix(0.4, @selected_bg_color, shade(0.7, @bg_color))
- fg[NORMAL] = shade(0.5, @fg_color)
- text[NORMAL] = shade(0.5, @text_color)
-}
-style "dialog-button" = "dark"
-{
- bg[NORMAL] = shade(0.15, @bg_color)
- bg[PRELIGHT] = shade(0.18, @bg_color)
-}
-
-include"styles/checkradiobutton"
-include"styles/menu-menubar"
-include"styles/notebook"
-include"styles/scrollbar"
-include"styles/tooltips"
-
-style "gnome-color-chooser-combobox"
-{
- text[NORMAL] = @fg_color
- text[PRELIGHT] = mix (0.8, @fg_color, '#ffffff')
-}
-widget_class "*.<GtkComboBox>.<GtkCellView>" style "gnome-color-chooser-combobox"
-class "TerminalScreen" style "terminal"
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/checkradiobutton b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/checkradiobutton
deleted file mode 100644
index c62a9a8ab..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/checkradiobutton
+++ /dev/null
@@ -1,179 +0,0 @@
-style "checkbutton" = "default"
-{
- engine "pixmap"
- {
- image
- {
- function = CHECK
- recolorable = TRUE
- state = NORMAL
- shadow = OUT
- overlay_file = "Check-Radio/check1.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = CHECK
- recolorable = TRUE
- state = PRELIGHT
- shadow = OUT
- overlay_file = "Check-Radio/check3.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = CHECK
- recolorable = TRUE
- state = ACTIVE
- shadow = OUT
- overlay_file = "Check-Radio/check3.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = CHECK
- recolorable = TRUE
- state = INSENSITIVE
- shadow = OUT
- overlay_file = "Check-Radio/check1.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = CHECK
- recolorable = TRUE
- state = NORMAL
- shadow = IN
- overlay_file = "Check-Radio/check2.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = CHECK
- recolorable = TRUE
- state = PRELIGHT
- shadow = IN
- overlay_file = "Check-Radio/check4.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = CHECK
- recolorable = TRUE
- state = ACTIVE
- shadow = IN
- overlay_file = "Check-Radio/check4.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = CHECK
- recolorable = TRUE
- state = INSENSITIVE
- shadow = IN
- overlay_file = "Check-Radio/check5.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = FLAT_BOX
- recolorable = TRUE
- stretch = TRUE
- file = "Check-Radio/checklight.png"
- border = { 2, 2, 2, 2 }
- }
- }
-}
-
-style "radiobutton" = "default"
-{
- engine "pixmap"
- {
- image
- {
- function = OPTION
- recolorable = TRUE
- state = NORMAL
- shadow = OUT
- overlay_file = "Check-Radio/option1.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = OPTION
- recolorable = TRUE
- state = PRELIGHT
- shadow = OUT
- overlay_file = "Check-Radio/option3.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = OPTION
- recolorable = TRUE
- state = ACTIVE
- shadow = OUT
- overlay_file = "Check-Radio/option3.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = OPTION
- recolorable = TRUE
- state = INSENSITIVE
- shadow = OUT
- overlay_file = "Check-Radio/option1.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = OPTION
- recolorable = TRUE
- state = NORMAL
- shadow = IN
- overlay_file = "Check-Radio/option2.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = OPTION
- recolorable = TRUE
- state = PRELIGHT
- shadow = IN
- overlay_file = "Check-Radio/option4.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = OPTION
- recolorable = TRUE
- state = ACTIVE
- shadow = IN
- overlay_file = "Check-Radio/option4.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = OPTION
- recolorable = TRUE
- state = INSENSITIVE
- shadow = IN
- overlay_file = "Check-Radio/option1.png"
- overlay_stretch = FALSE
- }
- image
- {
- function = FLAT_BOX
- recolorable = TRUE
- stretch = TRUE
- file = "Check-Radio/checklight.png"
- border = { 2, 2, 2, 2 }
- }
- }
-}
-
-
-class "GtkRadioButton" style "radiobutton"
-class "GtkRadioMenuItem" style "radiobutton"
-class "GtkCheckButton" style "checkbutton"
-class "GtkCheckMenuItem" style "checkbutton"
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/menu-menubar b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/menu-menubar
deleted file mode 100644
index a15363a9b..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/menu-menubar
+++ /dev/null
@@ -1,50 +0,0 @@
-style "menu" = "default"
-{
- xthickness = 2
- ythickness = 2
-
- bg[PRELIGHT] = shade (1.04, @selected_bg_color)
-}
-
-style "menuitem"
-{
- xthickness = 0
- ythickness = 0
-
- engine "murrine" {
- roundness = 1
- border_colors = {"#7C7C7C", "#7C7C7C"}
- border_shades = {1.0, 1.0} # draw a gradient on the border.
- gradient_shades = {1.0,1.0,1.0,1.0}
- }
-}
-
-style "menubar" = "dark-toolbar"
-{
- ythickness = 0
- xthickness = 0
-
- bg[PRELIGHT] = shade (1.04, @selected_bg_color)
-
- engine "murrine" {
- roundness = 1
- border_shades = {1.0, 1.0} # draw a gradient on the border.
- border_colors = {"#7C7C7C", "#7C7C7C"}
- glowstyle = 0
- gradient_shades = {1.0,1.0,1.0,1.0}
- highlight_shade = 1.0
- lightborder_shade = 1.0
- reliefstyle = 0 # 0 = flat, 1 = inset, 2 = shadow, = 3 for a gradient on shadow, = 4 for a stronger shadow.
- }
-}
-
-class "GtkMenu" style "menu"
-class "GtkMenuBar*" style "menubar"
-class "GtkMenuItem" style "menuitem"
-class "GtkTearoffMenuItem" style "menuitem"
-
-widget_class "*GtkMenu.*" style "menu"
-widget_class "*MenuBar.*" style "menubar"
-widget_class "*.<MenuItem>." style "menuitem"
-# The panel menubar
-widget_class "*Panel*<GtkMenuBar>*" style "menubar"
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/notebook b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/notebook
deleted file mode 100644
index 4c52077ed..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/notebook
+++ /dev/null
@@ -1,29 +0,0 @@
-style "notebook-close" {
- stock["gtk-close"] = {
- { "Icons/close.png", *, *, * }
- }
-}
-
-style "notebook" = "wider" {
-
- bg[NORMAL] = shade (1.0615, @bg_color)
- bg[ACTIVE] = shade (0.85, @bg_color)
-
- engine "murrine" {
-
- lightborder_shade = 1.1
- highlight_shade = 1.01
- }
-}
-
-style "notebookthin" = "notebook" {
-
- xthickness = 2
- ythickness = 2
-}
-
-widget_class "*<GtkNotebook>*<GtkEventBox>" style "notebook"
-widget_class "*<GtkNotebook>*<GtkDrawingArea>" style "notebook"
-widget_class "*<GtkNotebook>*<GtkLayout>" style "notebook"
-widget_class "*<GtkNotebook>" style "notebook"
-widget_class "*<GtkNotebook>*" style "notebook-close"
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/scrollbar b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/scrollbar
deleted file mode 100644
index aa39423f5..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/scrollbar
+++ /dev/null
@@ -1,125 +0,0 @@
-style "scrollbar" = "default"
-{
- engine "pixmap"
- {
- image
- {
- function = BOX
- recolorable = TRUE
- detail = "trough"
- file = "Scrollbars/trough-scrollbar-horiz.png"
- border = { 20, 20, 0, 0 }
- stretch = TRUE
- orientation = HORIZONTAL
- }
- image
- {
- function = BOX
- recolorable = TRUE
- detail = "trough"
- file = "Scrollbars/trough-scrollbar-vert.png"
- border = { 0, 0, 20, 20 }
- stretch = TRUE
- orientation = VERTICAL
- }
-###########x SLIDERS ##################x
-
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = NORMAL
- file = "Scrollbars/slider-horiz.png"
- border = { 10, 10, 0, 0 }
- stretch = TRUE
- orientation = HORIZONTAL
- }
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = ACTIVE
- shadow = IN
- file = "Scrollbars/slider-horiz.png"
- border = { 10, 10, 0, 0 }
- stretch = TRUE
- orientation = HORIZONTAL
-
- }
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = PRELIGHT
- file = "Scrollbars/slider-horiz-prelight.png"
- border = { 10, 10, 0, 0 }
- stretch = TRUE
- orientation = HORIZONTAL
-
- }
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = INSENSITIVE
- file = "Scrollbars/slider-horiz-insens.png"
- border = { 10, 10, 0, 0 }
- stretch = TRUE
- orientation = HORIZONTAL
-
- }
-#############x verticals################xx
-
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = NORMAL
- file = "Scrollbars/slider-vert.png"
- border = { 0, 0, 10, 10 }
- stretch = TRUE
- orientation = VERTICAL
-
- }
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = ACTIVE
- shadow = IN
- file = "Scrollbars/slider-vert.png"
- border = { 0, 0, 10, 10 }
- stretch = TRUE
- orientation = VERTICAL
-
- }
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = PRELIGHT
- file = "Scrollbars/slider-vert-prelight.png"
- border = { 0, 0, 10, 10 }
- stretch = TRUE
- orientation = VERTICAL
-
- }
- image
- {
- function = SLIDER
- recolorable = TRUE
- state = INSENSITIVE
- file = "Scrollbars/slider-vert-insens.png"
- border = { 0, 0, 10, 10 }
- stretch = TRUE
- orientation = VERTICAL
-
- }
-
-########### END SLIDERS ##################
- }
-}
-
-class "GtkScrollbar" style "scrollbar"
-class "GtkVScrollbar" style "scrollbar"
-class "GtkHScrollbar" style "scrollbar"
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/tooltips b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/tooltips
deleted file mode 100644
index 6658f062f..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/gtk-2.0/styles/tooltips
+++ /dev/null
@@ -1,21 +0,0 @@
-style "tooltips"
-{
- xthickness = 4
- ythickness = 4
-
- GtkWidget::new-tooltip-style = 1
-
- bg[NORMAL] = @tooltip_bg_color
- fg[NORMAL] = @tooltip_fg_color
-}
-
-
-widget "gtk-tooltips" style "tooltips"
-class "*GtkTooltips*" style "tooltips"
-widget_class "*Tooltips*" style "tooltips"
-widget "*.nautilus-extra-view-widget" style:highest "tooltips"
-style "nautilusrename" {
-# fg[NORMAL] = "#e1e1e1"
-}
-
-widget_class "*.EelEditableLabel" style "nautilusrename"
diff --git a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/index.theme b/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/index.theme
deleted file mode 100644
index d5d631b0f..000000000
--- a/packaging/win32/DelugeStart Theme/share/themes/DelugeStart/index.theme
+++ /dev/null
@@ -1,12 +0,0 @@
-[Desktop Entry]
-Version=1.2
-Encoding=UTF-8
-Name=DelugeStart
-Type=X-GNOME-Metatheme
-Comment=DelugeStart theme by alecive
-
-[X-GNOME-Metatheme]
-GtkTheme=DelugeStart
-IconTheme=AwOken-303030
-CursorTheme=DMZ-White
-ButtonLayout=menu:minimize,maximize,close
diff --git a/packaging/win32/Win32 README.txt b/packaging/win32/Win32 README.txt
deleted file mode 100644
index d74e02278..000000000
--- a/packaging/win32/Win32 README.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-= Deluge Installer for Windows =
-
-Instructions for building the Deluge NSIS Installer for Windows XP/Vista/7.
-
-== Dependencies ==
- * Deluge build: http://dev.deluge-torrent.org/wiki/Installing/Source#WindowsDependencies
- * Bbfreeze: http://pypi.python.org/pypi/bbfreeze
- * NSIS: http://nsis.sourceforge.net/Download
-
-== Build Steps ==
-
- 1. Build and Install Deluge on Windows.
-
- 2. Run the bbfreeze script from the win32 directory:
-
- python deluge-bbfreeze.py
-
- The result is a bbfreeze'd version of Deluge in `build-win32/deluge-bbfreeze-build_version`.
-
- 3. Run the NSIS script (right-click and choose `Compile with NSIS`)
-
- The result is a standalone installer in the `build-win32` directory.
diff --git a/packaging/win32/deluge-bbfreeze.py b/packaging/win32/deluge-bbfreeze.py
deleted file mode 100644
index 5cd3e35a7..000000000
--- a/packaging/win32/deluge-bbfreeze.py
+++ /dev/null
@@ -1,264 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2012-2015 Calum Lind <calumlind@gmail.com>
-# Copyright (C) 2010 Damien Churchill <damoxc@gmail.com>
-# Copyright (C) 2009-2010 Andrew Resch <andrewresch@gmail.com>
-# Copyright (C) 2009 Jesper Lund <mail@jesperlund.com>
-#
-# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
-# the additional special exception to link portions of this program with the OpenSSL library.
-# See LICENSE for more details.
-#
-# isort:skip_file
-from __future__ import print_function
-
-import glob
-import os
-import re
-import shutil
-import sys
-
-
-import bbfreeze
-import gtk
-from win32verstamp import stamp
-
-import deluge.common
-
-
-class VersionInfo(object):
- def __init__(
- self,
- version,
- internalname=None,
- originalfilename=None,
- comments=None,
- company=None,
- description=None,
- _copyright=None,
- trademarks=None,
- product=None,
- dll=False,
- debug=False,
- verbose=True,
- ):
- parts = version.split('.')
- while len(parts) < 4:
- parts.append('0')
- self.version = '.'.join(parts)
- self.internal_name = internalname
- self.original_filename = originalfilename
- self.comments = comments
- self.company = company
- self.description = description
- self.copyright = _copyright
- self.trademarks = trademarks
- self.product = product
- self.dll = dll
- self.debug = debug
- self.verbose = verbose
-
-
-DEBUG = False
-if len(sys.argv) == 2 and sys.argv[1].lower() == 'debug':
- DEBUG = True
-
-# Get build_version from installed deluge.
-build_version = deluge.common.get_version()
-python_path = os.path.dirname(sys.executable)
-if python_path.endswith('Scripts'):
- python_path = python_path[:-8]
-gtk_root = os.path.join(gtk.__path__[0], '..', 'runtime')
-build_dir = os.path.join('build-win32', 'deluge-bbfreeze-' + build_version)
-
-if DEBUG:
- print('Python Path: %s' % python_path)
- print('Gtk Path: %s' % gtk_root)
- print('bbfreeze Output Path: %s' % build_dir)
-
-print('Freezing Deluge %s...' % build_version)
-# Disable printing to console for bbfreezing.
-if not DEBUG:
- sys.stdout = open(os.devnull, 'w')
-
-# Include python modules not picked up automatically by bbfreeze.
-includes = (
- 'libtorrent',
- 'cairo',
- 'pangocairo',
- 'atk',
- 'pango',
- 'twisted.internet.utils',
- 'gio',
- 'gzip',
- 'email.mime.multipart',
- 'email.mime.text',
- '_cffi_backend',
-)
-excludes = ('numpy', 'OpenGL', 'psyco', 'win32ui', 'unittest')
-
-
-def recipe_gtk_override(mf):
- # Override bbfreeze function so that it includes all gtk libraries
- # in the installer so users don't require a separate GTK+ installation.
- return True
-
-
-bbfreeze.recipes.recipe_gtk_and_friends = recipe_gtk_override
-
-# Workaround for "ImportError: The 'packaging' package is required" with setuptools > 18.8.
-# (https://github.com/pypa/setuptools/issues/517)
-bbfreeze.recipes.recipe_pkg_resources = bbfreeze.recipes.include_whole_package(
- 'pkg_resources'
-)
-
-fzr = bbfreeze.Freezer(build_dir, includes=includes, excludes=excludes)
-fzr.include_py = False
-fzr.setIcon(
- os.path.join(
- os.path.dirname(deluge.common.__file__), 'ui', 'data', 'pixmaps', 'deluge.ico'
- )
-)
-
-# TODO: Can/should we grab the script list from setup.py entry_points somehow.
-
-# Hide cmd console popup for these console entries force gui_script True.
-force_gui = ['deluge-web', 'deluged']
-
-for force_script in force_gui:
- script_path = os.path.join(python_path, 'Scripts', force_script + '-script.py')
- shutil.copy(script_path, script_path.replace('script', 'debug-script'))
-
-script_list = []
-for script in glob.glob(os.path.join(python_path, 'Scripts\\deluge*-script.py*')):
- # Copy the scripts to remove the '-script' suffix before adding to freezer.
- new_script = script.replace('-script', '')
- shutil.copy(script, new_script)
-
- gui_script = False
- script_splitext = os.path.splitext(os.path.basename(new_script))
- if script_splitext[1] == '.pyw' or script_splitext[0] in force_gui:
- gui_script = True
- try:
- fzr.addScript(new_script, gui_only=gui_script)
- script_list.append(new_script)
- except Exception:
- os.remove(script)
-
-# Start the freezing process.
-fzr()
-
-# Clean up the duplicated scripts.
-for script in script_list:
- os.remove(script)
-
-# Exclude files which are already included in GTK or Windows. Also exclude unneeded pygame dlls.
-exclude_dlls = (
- 'MSIMG32.dll',
- 'MSVCR90.dll',
- 'MSVCP90.dll',
- 'MSVCR120.dll',
- 'POWRPROF.dll',
- 'DNSAPI.dll',
- 'USP10.dll',
- 'MPR.dll',
- 'jpeg.dll',
- 'libfreetype-6.dll',
- 'libpng12-0.dll',
- 'libtiff.dll',
- 'SDL_image.dll',
- 'SDL_ttf.dll',
-)
-for exclude_dll in exclude_dlls:
- try:
- os.remove(os.path.join(build_dir, exclude_dll))
- except OSError:
- pass
-
-# Re-enable printing.
-if not DEBUG:
- sys.stdout = sys.__stdout__
-
-# Copy gtk locale files.
-gtk_locale = os.path.join(gtk_root, 'share/locale')
-locale_include_list = ['gtk20.mo', 'locale.alias']
-
-
-def ignored_files(adir, ignore_filenames):
- return [
- ignore_file
- for ignore_file in ignore_filenames
- if not os.path.isdir(os.path.join(adir, ignore_file))
- and ignore_file not in locale_include_list
- ]
-
-
-shutil.copytree(
- gtk_locale, os.path.join(build_dir, 'share/locale'), ignore=ignored_files
-)
-
-# Copy gtk theme files.
-theme_include_list = [
- [gtk_root, 'share/icons/hicolor/index.theme'],
- [gtk_root, 'lib/gtk-2.0/2.10.0/engines'],
- [gtk_root, 'share/themes/MS-Windows'],
- ['DelugeStart Theme', 'lib/gtk-2.0/2.10.0/engines/libmurrine.dll'],
- ['DelugeStart Theme', 'share/themes/DelugeStart'],
- ['DelugeStart Theme', 'etc/gtk-2.0/gtkrc'],
-]
-for path_root, path in theme_include_list:
- full_path = os.path.join(path_root, path)
- if os.path.isdir(full_path):
- shutil.copytree(full_path, os.path.join(build_dir, path))
- else:
- dst_dir = os.path.join(build_dir, os.path.dirname(path))
- try:
- os.makedirs(dst_dir)
- except OSError:
- pass
- shutil.copy(full_path, dst_dir)
-
-# Add version information to exe files.
-for script in script_list:
- script_exe = os.path.splitext(os.path.basename(script))[0] + '.exe'
- # Don't add to dev build versions.
- if not re.search('[a-zA-Z_-]', build_version):
- version_info = VersionInfo(
- build_version,
- description='Deluge Bittorrent Client',
- company='Deluge Team',
- product='Deluge',
- _copyright='Deluge Team',
- )
- stamp(os.path.join(build_dir, script_exe), version_info)
-
-# Copy version info to file for nsis script.
-with open('VERSION.tmp', 'w') as ver_file:
- ver_file.write('build_version = "%s"' % build_version)
-
-# Create the install and uninstall file list for NSIS.
-filedir_list = []
-for root, dirnames, filenames in os.walk(build_dir):
- dirnames.sort()
- filenames.sort()
- filedir_list.append((root[len(build_dir) :], filenames))
-
-with open('install_files.nsh', 'w') as f:
- f.write('; Files to install\n')
- for dirname, files in filedir_list:
- if not dirname:
- dirname = os.sep
- f.write('\nSetOutPath "$INSTDIR%s"\n' % dirname)
- for filename in files:
- f.write('File "${BBFREEZE_DIR}%s"\n' % os.path.join(dirname, filename))
-
-with open('uninstall_files.nsh', 'w') as f:
- f.write('; Files to uninstall\n')
- for dirname, files in reversed(filedir_list):
- f.write('\n')
- if not dirname:
- dirname = os.sep
- for filename in files:
- f.write('Delete "$INSTDIR%s"\n' % os.path.join(dirname, filename))
- f.write('RMDir "$INSTDIR%s"\n' % dirname)
diff --git a/pyproject.toml b/pyproject.toml
index 2674219a7..67ebe0a0c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,5 +9,3 @@ skip-string-normalization = true
[tool.isort]
profile = "black"
-# Python 2 stdlib
-extra_standard_library = ["urlparse", "HTMLParser", "urllib2"]
diff --git a/requirements-tests.txt b/requirements-tests.txt
index 705d96774..5e8fb20ad 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -1,4 +1,4 @@
-pytest != 5.2.3, < 5.4
+pytest
pytest-twisted
pytest-cov
mock
diff --git a/requirements.txt b/requirements.txt
index c414829b9..7df660fcf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,14 +1,17 @@
+libtorrent
twisted[tls]>=17.1
rencode
pyopenssl
pyxdg
pillow
mako
-chardet
-six
+setuptools
+chardet==4.0.0
setproctitle
pywin32; sys_platform == 'win32'
certifi; sys_platform == 'win32'
windows-curses; sys_platform == 'win32'
zope.interface>=4.4.2
distro; 'linux' in sys_platform or 'bsd' in sys_platform
+pygeoip
+ifaddr==0.2.0
diff --git a/setup.cfg b/setup.cfg
index b4071f53e..9403fb0cb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,15 +27,19 @@ frameworks = CoreFoundation, Foundation, AppKit
[flake8]
max-line-length = 120
builtins = _,_n,__request__
-exclude = .git,.tox,.eggs,dist,build
-ignore =
-# A003 Class attribute is a python builtin.
+extend-exclude = dist,build
+extend-ignore =
+# flake8-builtins: A003 class attribute is shadowing a python builtin
A003,
-# C813, C815, C816: PY3 missing trailing commas.
- C813,C815,C816,
-# W503 line break before binary operator.
- W503,
- E203
-
+# E203 whitespace before ':'
+ E203,
+# N818 pep8-naming: error suffix in exception names
+ N818
+per-file-ignores =
+# import not top of file (gi checks required before import)
+ deluge/ui/gtk3/*.py : E402
+ deluge/**/gtkui.py: E402
+ deluge/**/gtkui/*.py: E402
+ deluge/plugins/Stats/deluge_stats/graph.py: E402
[pycodestyle]
max-line-length = 88
diff --git a/setup.py b/setup.py
index ef8d38e21..6a441f745 100755
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
@@ -9,8 +8,6 @@
# See LICENSE for more details.
#
-from __future__ import print_function
-
import glob
import os
import platform
@@ -18,7 +15,7 @@ import sys
from distutils.command.build import build as _build
from distutils.command.clean import clean as _clean
from distutils.command.install_data import install_data as _install_data
-from shutil import rmtree
+from shutil import rmtree, which
from setuptools import Command, find_packages, setup
from setuptools.command.test import test as _test
@@ -27,16 +24,10 @@ import msgfmt
from version import get_version
try:
- from shutil import which
-except ImportError:
- # PY2 Fallback
- from distutils.spawn import find_executable as which
-
-try:
from sphinx.setup_command import BuildDoc
except ImportError:
- class BuildDoc(object):
+ class BuildDoc:
pass
@@ -88,7 +79,7 @@ class CleanDocs(Command):
def run(self):
docs_build = 'docs/build'
- print('Deleting {}'.format(docs_build))
+ print(f'Deleting {docs_build}')
try:
rmtree(docs_build)
except OSError:
@@ -166,7 +157,7 @@ class CleanWebUI(Command):
for js_src_dir in BuildWebUI.JS_SRC_DIRS:
for file_type in ('.js', '-debug.js'):
js_file = os.path.join(js_basedir, js_src_dir + file_type)
- print('Deleting {}'.format(js_file))
+ print(f'Deleting {js_file}')
try:
os.remove(js_file)
except OSError:
@@ -174,7 +165,7 @@ class CleanWebUI(Command):
# Remove generated gettext.js
js_file = os.path.join(js_basedir, 'gettext.js')
- print('Deleting {}'.format(js_file))
+ print(f'Deleting {js_file}')
try:
os.remove(js_file)
except OSError:
@@ -398,7 +389,7 @@ class Build(_build):
try:
from deluge._libtorrent import LT_VERSION
- print('Info: Found libtorrent ({}) installed.'.format(LT_VERSION))
+ print(f'Info: Found libtorrent ({LT_VERSION}) installed.')
except ImportError as ex:
print('Warning: libtorrent (libtorrent-rasterbar) not found: %s' % ex)
@@ -463,7 +454,7 @@ if not windows_check() and not osx_check():
for icon_path in glob.glob('deluge/ui/data/icons/hicolor/*x*'):
size = os.path.basename(icon_path)
icons = glob.glob(os.path.join(icon_path, 'apps', 'deluge*.png'))
- _data_files.append(('share/icons/hicolor/{}/apps'.format(size), icons))
+ _data_files.append((f'share/icons/hicolor/{size}/apps', icons))
_data_files.extend(
[
(
@@ -488,11 +479,21 @@ if not windows_check() and not osx_check():
if os.path.isfile(appdata_data):
_data_files.append(('share/appdata', [appdata_data]))
+
+# Entry Points
_entry_points['console_scripts'] = [
'deluge-console = deluge.ui.console:start',
+]
+
+# On Windows use gui_scripts to hide cmd popup (no effect on Linux/MacOS)
+_entry_points['gui_scripts'] = [
+ 'deluge = deluge.ui.ui_entry:start_ui',
+ 'deluge-gtk = deluge.ui.gtk3:start',
'deluge-web = deluge.ui.web:start',
'deluged = deluge.core.daemon_entry:start_daemon',
]
+
+# Provide Windows 'debug' exes for stdin/stdout e.g. logging/errors
if windows_check():
_entry_points['console_scripts'].extend(
[
@@ -501,10 +502,7 @@ if windows_check():
'deluged-debug = deluge.core.daemon_entry:start_daemon',
]
)
-_entry_points['gui_scripts'] = [
- 'deluge = deluge.ui.ui_entry:start_ui',
- 'deluge-gtk = deluge.ui.gtk3:start',
-]
+
_entry_points['deluge.ui'] = [
'console = deluge.ui.console:Console',
'web = deluge.ui.web:Web',
@@ -548,7 +546,6 @@ install_requires = [
'pyopenssl',
'pyxdg',
'mako',
- 'six',
'setuptools',
"pywin32; sys_platform == 'win32'",
"certifi; sys_platform == 'win32'",
@@ -559,6 +556,7 @@ extras_require = {
'setproctitle',
'pillow',
'chardet',
+ 'ifaddr',
]
}
@@ -599,7 +597,7 @@ setup(
'Operating System :: POSIX',
'Topic :: Internet',
],
- python_requires='>=2.7',
+ python_requires='>=3.6',
license='GPLv3+',
cmdclass=cmdclass,
setup_requires=setup_requires,
diff --git a/tox.ini b/tox.ini
index 5b9152978..405610d7c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@
# Usage: `pip install tox` and then run `tox` from this directory.
[tox]
-envlist = py27, py3, lint, docs
+envlist = py3, lint, docs
minversion=3.0
[pytest]
@@ -81,12 +81,6 @@ commands = pytest -m "gtkui" deluge/tests
[testenv:todo]
commands = pytest -m "todo" deluge/tests
-[testenv:trial]
-setenv = {[testenv]setenv}{:}{toxinidir}/deluge/tests
-commands =
- python -c "import libtorrent as lt; print(lt.__version__)"
- python -m twisted.trial --reporter=deluge-reporter deluge.tests
-
[testenv:plugins]
setenv = PYTHONPATH = {toxinidir}{:}{toxinidir}/deluge/plugins
commands =
diff --git a/version.py b/version.py
index ff8f684d5..26098605d 100755
--- a/version.py
+++ b/version.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
# Authors: Douglas Creager <dcreager@dcreager.net>
# Calum Lind <calumlind@gmail.com>
#
@@ -31,8 +30,6 @@
# include RELEASE-VERSION
#
-from __future__ import print_function, unicode_literals
-
import os
import subprocess
@@ -58,9 +55,9 @@ def call_git_describe(prefix='', suffix=''):
def get_version(prefix='deluge-', suffix='.dev0'):
try:
- with open(VERSION_FILE, 'r') as f:
+ with open(VERSION_FILE) as f:
release_version = f.readline().strip()
- except IOError:
+ except OSError:
release_version = None
version = call_git_describe(prefix, suffix)