From 92c36a468a120e8c20af25302bc8fde8e4f3eb85 Mon Sep 17 00:00:00 2001 From: drew2a Date: Fri, 20 Jan 2023 19:00:28 +0100 Subject: [PATCH] Refactor `TriblerNetworkRequest` --- .../core/utilities/limited_ordered_dict.py | 19 ++ .../tests/test_limited_ordered_dict.py | 27 ++ src/tribler/gui/core_manager.py | 6 +- src/tribler/gui/debug_window.py | 128 +++++--- .../gui/dialogs/addtopersonalchanneldialog.py | 54 ++-- .../gui/dialogs/createtorrentdialog.py | 27 +- src/tribler/gui/dialogs/editmetadatadialog.py | 15 +- src/tribler/gui/dialogs/feedbackdialog.py | 23 +- .../gui/dialogs/startdownloaddialog.py | 24 +- src/tribler/gui/network/__init__.py | 0 src/tribler/gui/network/request/__init__.py | 0 .../network/request/file_download_request.py | 14 + src/tribler/gui/network/request/request.py | 126 ++++++++ .../gui/network/request/shutdown_request.py | 8 + .../gui/network/request/tests/__init__.py | 0 .../gui/network/request/tests/test_request.py | 36 +++ src/tribler/gui/network/request_manager.py | 150 ++++++++++ .../gui/network/tests/test_request_manager.py | 44 +++ src/tribler/gui/tribler_request_manager.py | 275 ------------------ src/tribler/gui/tribler_window.py | 140 +++++---- .../gui/widgets/channelcontentswidget.py | 48 +-- .../gui/widgets/channeldescriptionwidget.py | 58 ++-- .../gui/widgets/channelsmenulistwidget.py | 27 +- src/tribler/gui/widgets/createtorrentpage.py | 30 +- .../gui/widgets/downloadsdetailstabwidget.py | 20 +- src/tribler/gui/widgets/downloadspage.py | 104 +++++-- src/tribler/gui/widgets/lazytableview.py | 14 +- .../gui/widgets/searchresultswidget.py | 30 +- src/tribler/gui/widgets/settingspage.py | 19 +- src/tribler/gui/widgets/tablecontentmodel.py | 106 ++++--- .../gui/widgets/triblertablecontrollers.py | 42 ++- src/tribler/gui/widgets/trustgraphpage.py | 41 +-- src/tribler/gui/widgets/trustpage.py | 9 +- 33 files changed, 1026 insertions(+), 638 deletions(-) create mode 100644 src/tribler/core/utilities/limited_ordered_dict.py create mode 100644 src/tribler/core/utilities/tests/test_limited_ordered_dict.py create mode 100644 src/tribler/gui/network/__init__.py create mode 100644 src/tribler/gui/network/request/__init__.py create mode 100644 src/tribler/gui/network/request/file_download_request.py create mode 100644 src/tribler/gui/network/request/request.py create mode 100644 src/tribler/gui/network/request/shutdown_request.py create mode 100644 src/tribler/gui/network/request/tests/__init__.py create mode 100644 src/tribler/gui/network/request/tests/test_request.py create mode 100644 src/tribler/gui/network/request_manager.py create mode 100644 src/tribler/gui/network/tests/test_request_manager.py delete mode 100644 src/tribler/gui/tribler_request_manager.py diff --git a/src/tribler/core/utilities/limited_ordered_dict.py b/src/tribler/core/utilities/limited_ordered_dict.py new file mode 100644 index 00000000000..9471fdcec14 --- /dev/null +++ b/src/tribler/core/utilities/limited_ordered_dict.py @@ -0,0 +1,19 @@ +from collections import OrderedDict + + +class LimitedOrderedDict(OrderedDict): + """ This class is an implementation of OrderedDict with size limit. + + If the size of the dict exceeds the limit, the oldest entries will be deleted. + """ + def __init__(self, *args, limit: int = 200, **kwargs): + self.limit = limit + super().__init__(*args, **kwargs) + + def __setitem__(self, key, value): + super().__setitem__(key, value) + self._adjust_size() + + def _adjust_size(self): + while len(self) > self.limit: + self.popitem(last=False) diff --git a/src/tribler/core/utilities/tests/test_limited_ordered_dict.py b/src/tribler/core/utilities/tests/test_limited_ordered_dict.py new file mode 100644 index 00000000000..9b0407b75b0 --- /dev/null +++ b/src/tribler/core/utilities/tests/test_limited_ordered_dict.py @@ -0,0 +1,27 @@ +from tribler.core.utilities.limited_ordered_dict import LimitedOrderedDict + + +def test_order(): + d = LimitedOrderedDict() + d['first'] = '1' + d['second'] = '2' + d['third'] = '3' + + assert list(d.keys()) == ['first', 'second', 'third'] + + +def test_limit(): + d = LimitedOrderedDict(limit=2) + d['first'] = '1' + d['second'] = '2' + d['third'] = '3' + + assert list(d.keys()) == ['second', 'third'] + + +def test_merge(): + d1 = {'first': 1, 'second': 2} + d2 = {'third': 3, 'fourth': 4} + + d = LimitedOrderedDict({**d1, **d2}, limit=2) + assert len(d) == 2 diff --git a/src/tribler/gui/core_manager.py b/src/tribler/gui/core_manager.py index 95102b01a91..3ded9da1ca3 100644 --- a/src/tribler/gui/core_manager.py +++ b/src/tribler/gui/core_manager.py @@ -14,7 +14,8 @@ from tribler.gui.app_manager import AppManager from tribler.gui.event_request_manager import EventRequestManager from tribler.gui.exceptions import CoreConnectTimeoutError, CoreCrashedError -from tribler.gui.tribler_request_manager import ShutdownRequest, request_manager +from tribler.gui.network.request.shutdown_request import ShutdownRequest +from tribler.gui.network.request_manager import request_manager from tribler.gui.utilities import connect @@ -237,7 +238,8 @@ def send_shutdown_request(initial=False): else: self._logger.warning("Re-sending shutdown request to Tribler Core") - ShutdownRequest(shutdown_request_processed, on_cancel=send_shutdown_request) + request = ShutdownRequest(shutdown_request_processed, on_cancel=send_shutdown_request) + request_manager.add(request) send_shutdown_request(initial=True) diff --git a/src/tribler/gui/debug_window.py b/src/tribler/gui/debug_window.py index b4a274a8439..75117c3be21 100644 --- a/src/tribler/gui/debug_window.py +++ b/src/tribler/gui/debug_window.py @@ -19,8 +19,9 @@ from tribler.gui.defs import DEBUG_PANE_REFRESH_TIMEOUT, GB, MB from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog from tribler.gui.event_request_manager import received_events as tribler_received_events +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.resource_monitor import GuiResourceMonitor -from tribler.gui.tribler_request_manager import TriblerNetworkRequest, performed_requests as tribler_performed_requests from tribler.gui.utilities import connect, format_size, get_ui_file_path from tribler.gui.widgets.graphs.timeseriesplot import TimeSeriesPlot from tribler.gui.widgets.ipv8health import MonitorWidget @@ -144,9 +145,15 @@ def hideEvent(self, hide_event): def showEvent(self, show_event): if self.ipv8_health_widget and self.ipv8_health_widget.isVisible(): self.ipv8_health_widget.resume() - TriblerNetworkRequest( - "ipv8/asyncio/drift", self.on_ipv8_health_enabled, data={"enable": True}, method='PUT' + request = Request( + endpoint="ipv8/asyncio/drift", + on_finish=self.on_ipv8_health_enabled, + data={ + "enable": True + }, + method=Request.PUT ) + request_manager.add(request) def run_with_timer(self, call_fn, timeout=DEBUG_PANE_REFRESH_TIMEOUT): call_fn() @@ -252,7 +259,8 @@ def create_and_add_widget_item(self, key, value, widget): widget.addTopLevelItem(item) def load_general_tab(self): - TriblerNetworkRequest("statistics/tribler", self.on_tribler_statistics) + request = Request("statistics/tribler", self.on_tribler_statistics) + request_manager.add(request) def on_tribler_statistics(self, data): if not data: @@ -320,10 +328,10 @@ def show_gui_settings(self): def load_requests_tab(self): self.window().requests_tree_widget.clear() - for request, status_code in sorted(tribler_performed_requests, key=lambda rq: rq[0].time): - endpoint = request.url + for request, status_code in request_manager.performed_requests.items(): + endpoint = request.endpoint method = request.method - data = request.raw_data + data = request.data timestamp = request.time item = QTreeWidgetItem(self.window().requests_tree_widget) @@ -336,7 +344,8 @@ def load_bandwidth_accounting_tab(self) -> None: """ Initiate a request to the Tribler core to fetch statistics on bandwidth accounting. """ - TriblerNetworkRequest("bandwidth/statistics", self.on_bandwidth_statistics) + request = Request("bandwidth/statistics", self.on_bandwidth_statistics) + request_manager.add(request) def on_bandwidth_statistics(self, data: Dict) -> None: """ @@ -350,7 +359,8 @@ def on_bandwidth_statistics(self, data: Dict) -> None: self.create_and_add_widget_item(key, value, self.window().bandwidth_tree_widget) def load_ipv8_general_tab(self): - TriblerNetworkRequest("statistics/ipv8", self.on_ipv8_general_stats) + request = Request("statistics/ipv8", self.on_ipv8_general_stats) + request_manager.add(request) def on_ipv8_general_stats(self, data): if not data: @@ -364,7 +374,8 @@ def on_ipv8_general_stats(self, data): self.create_and_add_widget_item(key, value, self.window().ipv8_general_tree_widget) def load_ipv8_communities_tab(self): - TriblerNetworkRequest("ipv8/overlays", self.on_ipv8_community_stats) + request = Request("ipv8/overlays", self.on_ipv8_community_stats) + request_manager.add(request) def _colored_peer_count(self, peer_count, max_peers): limits = [20, max_peers + 1] @@ -440,7 +451,8 @@ def update_community_peers(self, item): def load_ipv8_community_details_tab(self): if self.ipv8_statistics_enabled: self.window().ipv8_statistics_error_label.setHidden(True) - TriblerNetworkRequest("ipv8/overlays/statistics", self.on_ipv8_community_detail_stats) + request = Request("ipv8/overlays/statistics", self.on_ipv8_community_detail_stats) + request_manager.add(request) else: self.window().ipv8_statistics_error_label.setHidden(False) self.window().ipv8_communities_details_widget.setHidden(True) @@ -489,7 +501,15 @@ def load_ipv8_health_monitor(self): # We already loaded the widget, just resume it. self.ipv8_health_widget.resume() # Whether the widget is newly loaded or not, start the measurements. - TriblerNetworkRequest("ipv8/asyncio/drift", self.on_ipv8_health_enabled, data={"enable": True}, method='PUT') + request = Request( + endpoint="ipv8/asyncio/drift", + on_finish=self.on_ipv8_health_enabled, + data={ + "enable": True + }, + method=Request.PUT + ) + request_manager.add(request) def hide_ipv8_health_widget(self): """ @@ -500,7 +520,12 @@ def hide_ipv8_health_widget(self): """ if self.ipv8_health_widget is not None and not self.ipv8_health_widget.is_paused: self.ipv8_health_widget.pause() - TriblerNetworkRequest("ipv8/asyncio/drift", lambda _: None, data={"enable": False}, method='PUT') + request = Request( + endpoint="ipv8/asyncio/drift", + data={"enable": False}, + method=Request.PUT + ) + request_manager.add(request) def on_ipv8_health(self, data): """ @@ -518,7 +543,12 @@ def on_ipv8_health_enabled(self, data): """ if not data: return - self.run_with_timer(lambda: TriblerNetworkRequest("ipv8/asyncio/drift", self.on_ipv8_health), 100) + + def send_request(): + request = Request("ipv8/asyncio/drift", self.on_ipv8_health) + request_manager.add(request) + + self.run_with_timer(send_request, 100) def add_items_to_tree(self, tree, items, keys): tree.clear() @@ -536,7 +566,8 @@ def add_items_to_tree(self, tree, items, keys): def load_tunnel_circuits_tab(self): self.window().circuits_tree_widget.setColumnWidth(3, 200) - TriblerNetworkRequest("ipv8/tunnel/circuits", self.on_tunnel_circuits) + request = Request("ipv8/tunnel/circuits", self.on_tunnel_circuits) + request_manager.add(request) def on_tunnel_circuits(self, circuits): if not circuits: @@ -553,7 +584,8 @@ def on_tunnel_circuits(self, circuits): ) def load_tunnel_relays_tab(self): - TriblerNetworkRequest("ipv8/tunnel/relays", self.on_tunnel_relays) + request = Request("ipv8/tunnel/relays", self.on_tunnel_relays) + request_manager.add(request) def on_tunnel_relays(self, data): if data: @@ -564,7 +596,8 @@ def on_tunnel_relays(self, data): ) def load_tunnel_exits_tab(self): - TriblerNetworkRequest("ipv8/tunnel/exits", self.on_tunnel_exits) + request = Request("ipv8/tunnel/exits", self.on_tunnel_exits) + request_manager.add(request) def on_tunnel_exits(self, data): if data: @@ -575,7 +608,8 @@ def on_tunnel_exits(self, data): ) def load_tunnel_swarms_tab(self): - TriblerNetworkRequest("ipv8/tunnel/swarms", self.on_tunnel_swarms) + request = Request("ipv8/tunnel/swarms", self.on_tunnel_swarms) + request_manager.add(request) def on_tunnel_swarms(self, data): if data: @@ -596,7 +630,8 @@ def on_tunnel_swarms(self, data): def load_tunnel_peers_tab(self): self.window().peers_tree_widget.setColumnWidth(2, 300) - TriblerNetworkRequest("ipv8/tunnel/peers", self.on_tunnel_peers) + request = Request("ipv8/tunnel/peers", self.on_tunnel_peers) + request_manager.add(request) def on_tunnel_peers(self, data): if data: @@ -605,7 +640,8 @@ def on_tunnel_peers(self, data): ) def load_dht_statistics_tab(self): - TriblerNetworkRequest("ipv8/dht/statistics", self.on_dht_statistics) + request = Request("ipv8/dht/statistics", self.on_dht_statistics) + request_manager.add(request) def on_dht_statistics(self, data): if not data: @@ -615,7 +651,8 @@ def on_dht_statistics(self, data): self.create_and_add_widget_item(key, value, self.window().dhtstats_tree_widget) def load_dht_buckets_tab(self): - TriblerNetworkRequest("ipv8/dht/buckets", self.on_dht_buckets) + request = Request("ipv8/dht/buckets", self.on_dht_buckets) + request_manager.add(request) def on_dht_buckets(self, data): if data: @@ -661,7 +698,8 @@ def load_open_files_tab(self): except psutil.AccessDenied as exc: gui_item.setText(0, f"Unable to get open files for GUI ({exc})") - TriblerNetworkRequest("debug/open_files", self.on_core_open_files) + request = Request("debug/open_files", self.on_core_open_files) + request_manager.add(request) def on_core_open_files(self, data): if not data: @@ -677,7 +715,8 @@ def on_core_open_files(self, data): core_item.addChild(item) def load_open_sockets_tab(self): - TriblerNetworkRequest("debug/open_sockets", self.on_core_open_sockets) + request = Request("debug/open_sockets", self.on_core_open_sockets) + request_manager.add(request) def on_core_open_sockets(self, data): if not data: @@ -703,7 +742,8 @@ def on_core_open_sockets(self, data): self.window().open_sockets_tree_widget.addTopLevelItem(item) def load_threads_tab(self): - TriblerNetworkRequest("debug/threads", self.on_core_threads) + request = Request("debug/threads", self.on_core_threads) + request_manager.add(request) def on_core_threads(self, data): if not data: @@ -741,7 +781,8 @@ def load_cpu_tab(self): def refresh_cpu_plot(self): # To update the core CPU graph, call Debug REST API to get the history # and update the CPU graph after receiving the response. - TriblerNetworkRequest("debug/cpu/history", self.on_core_cpu_history) + request = Request("debug/cpu/history", self.on_core_cpu_history) + request_manager.add(request) # GUI CPU graph can be simply updated using the data from GuiResourceMonitor object. self._update_cpu_graph(self.gui_cpu_plot, self.resource_monitor.get_cpu_history_dict()) @@ -779,7 +820,8 @@ def load_memory_tab(self): def load_profiler_tab(self): self.window().toggle_profiler_button.setEnabled(False) - TriblerNetworkRequest("debug/profiler", self.on_profiler_info) + request = Request("debug/profiler", self.on_profiler_info) + request_manager.add(request) def on_profiler_info(self, data): if not data: @@ -795,7 +837,8 @@ def on_toggle_profiler_button_clicked(self, checked=False): self.toggling_profiler = True self.window().toggle_profiler_button.setEnabled(False) method = "DELETE" if self.profiler_enabled else "PUT" - TriblerNetworkRequest("debug/profiler", self.on_profiler_state_changed, method=method) + request = Request("debug/profiler", self.on_profiler_state_changed, method=method) + request_manager.add(request) def on_profiler_state_changed(self, data): if not data: @@ -812,7 +855,8 @@ def on_profiler_state_changed(self, data): def refresh_memory_plot(self): # To update the core memory graph, call Debug REST API to get the history # and update the memory graph after receiving the response. - TriblerNetworkRequest("debug/memory/history", self.on_core_memory_history) + request = Request("debug/memory/history", self.on_core_memory_history) + request_manager.add(request) # GUI memory graph can be simply updated using the data from GuiResourceMonitor object. self._update_memory_graph(self.gui_memory_plot, self.resource_monitor.get_memory_history_dict()) @@ -831,7 +875,7 @@ def _update_memory_graph(self, memory_graph, history_data): def closeEvent(self, close_event): if self.rest_request: - self.rest_request.cancel_request() + self.rest_request.cancel() if self.cpu_plot_timer: self.cpu_plot_timer.stop() @@ -845,8 +889,15 @@ def load_logs_tab(self): tab_index = self.window().log_tab_widget.currentIndex() tab_name = "core" if tab_index == 0 else "gui" - params = {'process': tab_name, 'max_lines': max_log_lines} - TriblerNetworkRequest(f"debug/log", url_params=params, reply_callback=self.display_logs) + request = Request( + endpoint="debug/log", + on_finish=self.display_logs, + url_params={ + 'process': tab_name, + 'max_lines': max_log_lines + }, + ) + request_manager.add(request) def display_logs(self, data): if not data: @@ -899,9 +950,11 @@ def load_libtorrent_data(self, export=False): self.load_libtorrent_sessions_tab(hop, export=export) def load_libtorrent_settings_tab(self, hop, export=False): - TriblerNetworkRequest( - "libtorrent/settings?hop=%d" % hop, lambda data: self.on_libtorrent_settings_received(data, export=export) + request = Request( + endpoint=f"libtorrent/settings?hop={hop}", + on_finish=lambda data: self.on_libtorrent_settings_received(data, export=export) ) + request_manager.add(request) self.window().libtorrent_settings_tree_widget.clear() def on_libtorrent_settings_received(self, data, export=False): @@ -916,9 +969,11 @@ def on_libtorrent_settings_received(self, data, export=False): self.save_to_file("libtorrent_settings.json", data) def load_libtorrent_sessions_tab(self, hop, export=False): - TriblerNetworkRequest( - "libtorrent/session?hop=%d" % hop, lambda data: self.on_libtorrent_session_received(data, export=export) + request = Request( + endpoint=f"libtorrent/session?hop={hop}", + on_finish=lambda data: self.on_libtorrent_session_received(data, export=export) ) + request_manager.add(request) self.window().libtorrent_session_tree_widget.clear() def on_libtorrent_session_received(self, data, export=False): @@ -962,7 +1017,8 @@ def on_channels_peers(self, data): widget.addTopLevelItem(channel_item) def load_channels_peers_tab(self): - TriblerNetworkRequest("remote_query/channels_peers", self.on_channels_peers) + request = Request("remote_query/channels_peers", self.on_channels_peers) + request_manager.add(request) def channels_tab_changed(self, index): if index == 0: diff --git a/src/tribler/gui/dialogs/addtopersonalchanneldialog.py b/src/tribler/gui/dialogs/addtopersonalchanneldialog.py index 6487018418e..320264ba5e5 100644 --- a/src/tribler/gui/dialogs/addtopersonalchanneldialog.py +++ b/src/tribler/gui/dialogs/addtopersonalchanneldialog.py @@ -4,10 +4,10 @@ from PyQt5.QtCore import pyqtSignal from tribler.core.components.metadata_store.db.serialization import CHANNEL_TORRENT, COLLECTION_NODE - from tribler.gui.dialogs.dialogcontainer import DialogContainer from tribler.gui.dialogs.new_channel_dialog import NewChannelDialog -from tribler.gui.tribler_request_manager import TriblerNetworkRequest +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.utilities import connect, get_ui_file_path @@ -47,12 +47,13 @@ def on_new_channel_response(self, response): def on_create_new_channel_clicked(self, checked): def create_channel_callback(channel_name=None): - TriblerNetworkRequest( - "channels/mychannel/0/channels", - self.on_new_channel_response, - method='POST', - raw_data=json.dumps({"name": channel_name}) if channel_name else None, + request = Request( + endpoint="channels/mychannel/0/channels", + on_finish=self.on_new_channel_response, + method=Request.POST, + data=json.dumps({"name": channel_name}) if channel_name else None, ) + request_manager.add(request) NewChannelDialog(self, create_channel_callback) @@ -62,22 +63,24 @@ def on_create_new_folder_clicked(self, checked): return channel_id = selected[0].id_ - url = ("channels/mychannel/%i" % channel_id) + ("/channels" if channel_id == 0 else "/collections") + postfix = "channels" if not channel_id else "collections" + endpoint = f"channels/mychannel/{channel_id}/{postfix}" def create_channel_callback(channel_name=None): - TriblerNetworkRequest( - url, - self.on_new_channel_response, - method='POST', - raw_data=json.dumps({"name": channel_name}) if channel_name else None, + request = Request( + endpoint, + on_finish=self.on_new_channel_response, + method=Request.POST, + data=json.dumps({"name": channel_name}) if channel_name else None, ) + request_manager.add(request) NewChannelDialog(self, create_channel_callback) def clear_channels_tree(self): # ACHTUNG! All running requests must always be cancelled first to prevent race condition! for rq in self.root_requests_list: - rq.cancel_request() + rq.cancel() self.dialog_widget.channels_tree_wt.clear() self.id2wt_mapping = {0: self.dialog_widget.channels_tree_wt} self.load_channel(0) @@ -99,19 +102,20 @@ def on_item_expanded(self, item): self.load_channel(channel_id) def load_channel(self, channel_id): - self.root_requests_list.append( - TriblerNetworkRequest( - "channels/mychannel/%i" % channel_id, - lambda x: self.on_channel_contents(x, channel_id), - url_params={ - "metadata_type": [CHANNEL_TORRENT, COLLECTION_NODE], - "first": 1, - "last": 1000, - "exclude_deleted": True, - }, - ) + request = Request( + endpoint=f"channels/mychannel/{channel_id}", + on_finish=lambda result: self.on_channel_contents(result, channel_id), + url_params={ + "metadata_type": [CHANNEL_TORRENT, COLLECTION_NODE], + "first": 1, + "last": 1000, + "exclude_deleted": True, + }, ) + self.root_requests_list.append(request) + request_manager.add(request) + def get_selected_channel_id(self): selected = self.dialog_widget.channels_tree_wt.selectedItems() return None if not selected else selected[0].id_ diff --git a/src/tribler/gui/dialogs/createtorrentdialog.py b/src/tribler/gui/dialogs/createtorrentdialog.py index 8cfd1febab6..c8ae7ab919d 100644 --- a/src/tribler/gui/dialogs/createtorrentdialog.py +++ b/src/tribler/gui/dialogs/createtorrentdialog.py @@ -7,8 +7,9 @@ from tribler.gui.defs import BUTTON_TYPE_NORMAL from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog from tribler.gui.dialogs.dialogcontainer import DialogContainer +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, get_ui_file_path, is_dir_writable, tr @@ -18,7 +19,6 @@ def __init__(self, parent): class CreateTorrentDialog(DialogContainer): - create_torrent_notification = pyqtSignal(dict) add_to_channel_selected = pyqtSignal(str) @@ -47,9 +47,9 @@ def __init__(self, parent): def close_dialog(self, checked=False): if self.rest_request1: - self.rest_request1.cancel_request() + self.rest_request1.cancel() if self.rest_request2: - self.rest_request2.cancel_request() + self.rest_request2.cancel() super().close_dialog() @@ -112,11 +112,22 @@ def on_create_clicked(self, checked): self.name = self.dialog_widget.create_torrent_name_field.text() description = self.dialog_widget.create_torrent_description_field.toPlainText() - post_data = {"name": self.name, "description": description, "files": files_list, "export_dir": export_dir} - url = ( - "createtorrent?download=1" if self.dialog_widget.seed_after_adding_checkbox.isChecked() else "createtorrent" + + is_seed = self.dialog_widget.seed_after_adding_checkbox.isChecked() + self.rest_request1 = Request( + endpoint='createtorrent', + on_finish=self.on_torrent_created, + method=Request.POST, + url_params={'download': 1} if is_seed else None, + data={ + "name": self.name, + "description": description, + "files": files_list, + "export_dir": export_dir + }, ) - self.rest_request1 = TriblerNetworkRequest(url, self.on_torrent_created, data=post_data, method='POST') + request_manager.add(self.rest_request1) + self.dialog_widget.edit_channel_create_torrent_progress_label.setText(tr("Creating torrent. Please wait...")) def on_torrent_created(self, result): diff --git a/src/tribler/gui/dialogs/editmetadatadialog.py b/src/tribler/gui/dialogs/editmetadatadialog.py index 1b375b38943..e804f360b9a 100644 --- a/src/tribler/gui/dialogs/editmetadatadialog.py +++ b/src/tribler/gui/dialogs/editmetadatadialog.py @@ -2,19 +2,18 @@ from typing import Dict, List from PyQt5 import uic -from PyQt5.QtCore import QModelIndex, QPoint, pyqtSignal, Qt +from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSignal from PyQt5.QtWidgets import QComboBox, QSizePolicy, QWidget from tribler.core.components.knowledge.db.knowledge_db import ResourceType from tribler.core.components.knowledge.knowledge_constants import MAX_RESOURCE_LENGTH, MIN_RESOURCE_LENGTH - from tribler.gui.defs import TAG_HORIZONTAL_MARGIN from tribler.gui.dialogs.dialogcontainer import DialogContainer -from tribler.gui.tribler_request_manager import TriblerNetworkRequest -from tribler.gui.utilities import connect, get_ui_file_path, tr, get_objects_with_predicate +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager +from tribler.gui.utilities import connect, get_objects_with_predicate, get_ui_file_path, tr from tribler.gui.widgets.tagbutton import TagButton - METADATA_TABLE_PREDICATES = [ResourceType.TITLE, ResourceType.DESCRIPTION, ResourceType.DATE, ResourceType.LANGUAGE] @@ -72,7 +71,11 @@ def __init__(self, parent: QWidget, index: QModelIndex) -> None: self.dialog_widget.content_name_label.setText(self.data_item["name"]) # Fetch suggestions - TriblerNetworkRequest(f"knowledge/{self.infohash}/tag_suggestions", self.on_received_tag_suggestions) + request = Request( + endpoint=f"knowledge/{self.infohash}/tag_suggestions", + on_finish=self.on_received_tag_suggestions + ) + request_manager.add(request) self.update_window() diff --git a/src/tribler/gui/dialogs/feedbackdialog.py b/src/tribler/gui/dialogs/feedbackdialog.py index 88810dd5c6b..44810badf79 100644 --- a/src/tribler/gui/dialogs/feedbackdialog.py +++ b/src/tribler/gui/dialogs/feedbackdialog.py @@ -16,9 +16,9 @@ from tribler.core.sentry_reporter.sentry_scrubber import SentryScrubber from tribler.core.sentry_reporter.sentry_tools import CONTEXT_DELIMITER, LONG_TEXT_DELIMITER from tribler.gui.event_request_manager import received_events +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import performed_requests as tribler_performed_requests from tribler.gui.utilities import connect, get_ui_file_path, tr if TYPE_CHECKING: @@ -27,14 +27,14 @@ class FeedbackDialog(AddBreadcrumbOnShowMixin, QDialog): def __init__( # pylint: disable=too-many-arguments, too-many-locals - self, - parent: TriblerWindow, - sentry_reporter: SentryReporter, - reported_error: ReportedError, - tribler_version, - start_time, - stop_application_on_close=True, - additional_tags=None, + self, + parent: TriblerWindow, + sentry_reporter: SentryReporter, + reported_error: ReportedError, + tribler_version, + start_time, + stop_application_on_close=True, + additional_tags=None, ): QDialog.__init__(self, parent) self.core_manager = parent.core_manager @@ -101,11 +101,12 @@ def add_item_to_info_widget(key, value): # Add recent requests to feedback dialog request_ind = 1 - for request, status_code in sorted(tribler_performed_requests, key=lambda rq: rq[0].time)[-30:]: + + for request, status_code in request_manager.performed_requests.items(): add_item_to_info_widget( 'request_%d' % request_ind, '%s %s %s (time: %s, code: %s)' - % (request.url, request.method, request.raw_data, request.time, status_code), + % (request.endpoint, request.method, request.data, request.time, status_code), ) request_ind += 1 diff --git a/src/tribler/gui/dialogs/startdownloaddialog.py b/src/tribler/gui/dialogs/startdownloaddialog.py index 9ee239db4f9..06288c38f5d 100644 --- a/src/tribler/gui/dialogs/startdownloaddialog.py +++ b/src/tribler/gui/dialogs/startdownloaddialog.py @@ -9,11 +9,11 @@ from PyQt5.QtWidgets import QFileDialog, QSizePolicy from tribler.core.utilities.rest_utils import FILE_SCHEME, MAGNET_SCHEME, scheme_from_url, url_to_path - from tribler.gui.defs import METAINFO_MAX_RETRIES, METAINFO_TIMEOUT from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog from tribler.gui.dialogs.dialogcontainer import DialogContainer -from tribler.gui.tribler_request_manager import TriblerNetworkRequest +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.utilities import ( connect, format_size, @@ -122,7 +122,7 @@ def __init__(self, parent, download_uri): def close_dialog(self, checked=False): if self.rest_request: - self.rest_request.cancel_request() + self.rest_request.cancel() if self.metainfo_fetch_timer: self.metainfo_fetch_timer.stop() @@ -145,9 +145,13 @@ def perform_files_request(self): params = {'uri': self.download_uri} if direct: params['hops'] = 0 - self.rest_request = TriblerNetworkRequest( - 'torrentinfo', self.on_received_metainfo, capture_core_errors=False, url_params=params + self.rest_request = Request( + endpoint='torrentinfo', + on_finish=self.on_received_metainfo, + url_params=params, + capture_errors=False, ) + request_manager.add(self.rest_request) if self.metainfo_retries <= METAINFO_MAX_RETRIES: fetch_mode = tr("directly") if direct else tr("anonymously") @@ -224,11 +228,11 @@ def update_torrent_size_label(self): label_text = tr("Torrent size: ") + format_size(total_files_size) else: label_text = ( - tr("Selected: ") - + format_size(selected_files_size) - + " / " - + tr("Total: ") - + format_size(total_files_size) + tr("Selected: ") + + format_size(selected_files_size) + + " / " + + tr("Total: ") + + format_size(total_files_size) ) self.dialog_widget.loading_files_label.setStyleSheet("color:#ffffff;") self.dialog_widget.loading_files_label.setText(label_text) diff --git a/src/tribler/gui/network/__init__.py b/src/tribler/gui/network/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/tribler/gui/network/request/__init__.py b/src/tribler/gui/network/request/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/tribler/gui/network/request/file_download_request.py b/src/tribler/gui/network/request/file_download_request.py new file mode 100644 index 00000000000..f2e9cff96b7 --- /dev/null +++ b/src/tribler/gui/network/request/file_download_request.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from PyQt5.QtNetwork import QNetworkRequest + +from tribler.gui.network.request.request import Request + + +class FileDownloadRequest(Request): + def __init__(self, *args, **kwargs): + super().__init__( + priority=QNetworkRequest.LowPriority, + raw_response=True, + *args, **kwargs + ) diff --git a/src/tribler/gui/network/request/request.py b/src/tribler/gui/network/request/request.py new file mode 100644 index 00000000000..f1c0cdaf12b --- /dev/null +++ b/src/tribler/gui/network/request/request.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import json +import logging +from time import time +from typing import Callable, Dict, Optional, TYPE_CHECKING, Union + +from PyQt5.QtCore import QObject, pyqtSignal +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest + +from tribler.gui.utilities import connect + +if TYPE_CHECKING: + from tribler.gui.network.request_manager import RequestManager + + +class Request(QObject): + GET = 'GET' + POST = 'POST' + PUT = 'PUT' + PATCH = 'PATCH' + DELETE = 'DELETE' + + # This signal is called if we receive some real reply from the request + # and if the user defined a callback to call on the received data. + # We implement the callback as a signal call and not as a direct callback + # because we want the request object be deleted independent of what happens + # during the callback call. + on_finished_signal = pyqtSignal(object) + on_cancel_signal = pyqtSignal() + + def __init__( + self, + endpoint: str, + on_finish: Callable = lambda _: None, + on_cancel: Callable = lambda: None, + url_params: Optional[Dict] = None, + data: Optional[Union[bytes, str, Dict]] = None, + method: str = GET, + capture_errors: bool = True, + priority=QNetworkRequest.NormalPriority, + raw_response: bool = False, + ): + super().__init__() + self.logger = logging.getLogger(self.__class__.__name__) + + self.endpoint = endpoint + self.url_params = url_params + + self.priority = priority + self.method = method + self.capture_errors = capture_errors + self.raw_response = raw_response + self.data: Optional[bytes] = data + self.raw_data = data + if isinstance(self.data, Dict): + self.data = json.dumps(data) + if isinstance(self.data, str): + self.data = self.data.encode('utf8') + + connect(self.on_finished_signal, on_finish) + connect(self.on_cancel_signal, on_cancel) + + self.reply: Optional[QNetworkReply] = None # to hold the associated QNetworkReply object + self.manager: Optional[RequestManager] = None + self.url: str = '' + + # Pass the newly created object to the manager singleton, so the object can be dispatched immediately + self.time = time() + + def _on_finished(self): + if not self.reply or not self.manager: + return + + self.logger.info(f'Finished: {self}') + try: + if status_code := self.reply.attribute(QNetworkRequest.HttpStatusCodeAttribute): + self.manager.update(self, status_code) + + data = bytes(self.reply.readAll()) + if self.raw_response: + self.logger.debug('Create a raw response') + header = self.reply.header(QNetworkRequest.ContentTypeHeader) + self.on_finished_signal.emit((data, header)) + return + + self.logger.debug('Create a json response') + result = json.loads(data) + is_error = 'error' in result + if is_error and self.capture_errors: + text = self.manager.show_error(self, result) + raise Warning(text) + + self.on_finished_signal.emit(result) + except Exception as e: # pylint: disable=broad-except + self.logger.exception(e) + self.cancel() + finally: + self._delete() + + def cancel(self): + """ + Cancel the request by aborting the reply handle and calling on_cancel if available. + """ + try: + self.logger.warning(f'Request was canceled: {self}') + if self.reply: + self.reply.abort() + + self.on_cancel_signal.emit() + finally: + self._delete() + + def _delete(self): + """ + Call Qt deletion procedure for the object and its member objects + and remove the object from the request_manager's list of requests in flight + """ + self.logger.debug(f'Delete for {self}') + + if self.manager: + self.manager.remove(self) + self.manager = None + + def __str__(self): + return f'{self.method} {self.url}' diff --git a/src/tribler/gui/network/request/shutdown_request.py b/src/tribler/gui/network/request/shutdown_request.py new file mode 100644 index 00000000000..d8c4def092d --- /dev/null +++ b/src/tribler/gui/network/request/shutdown_request.py @@ -0,0 +1,8 @@ +from PyQt5.QtNetwork import QNetworkRequest + +from tribler.gui.network.request.request import Request + + +class ShutdownRequest(Request): + def __init__(self, *args, **kwargs): + super().__init__("shutdown", *args, **kwargs, method="PUT", priority=QNetworkRequest.HighPriority) diff --git a/src/tribler/gui/network/request/tests/__init__.py b/src/tribler/gui/network/request/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/tribler/gui/network/request/tests/test_request.py b/src/tribler/gui/network/request/tests/test_request.py new file mode 100644 index 00000000000..75f064f05f7 --- /dev/null +++ b/src/tribler/gui/network/request/tests/test_request.py @@ -0,0 +1,36 @@ +from tribler.gui.network.request.request import Request + + +def test_default_constructor(): + request = Request( + endpoint='endpoint' + ) + assert request + + +def test_dict_data_constructor(): + """ Test that data becomes raw_data as an encoded json + """ + request = Request( + endpoint='endpoint', + data={ + 'key': 'value' + } + ) + assert request.data == b'{"key": "value"}' + + +def test_bytes_data_constructor(): + request = Request( + endpoint='endpoint', + data=b'bytes' + ) + assert request.data == b'bytes' + + +def test_str_data_constructor(): + request = Request( + endpoint='endpoint', + data='str' + ) + assert request.data == b'str' diff --git a/src/tribler/gui/network/request_manager.py b/src/tribler/gui/network/request_manager.py new file mode 100644 index 00000000000..68af07239c0 --- /dev/null +++ b/src/tribler/gui/network/request_manager.py @@ -0,0 +1,150 @@ +from __future__ import annotations + +import json +import logging +from time import time +from typing import Dict, TYPE_CHECKING +from urllib.parse import quote_plus + +from PyQt5.QtCore import QBuffer, QIODevice, QUrl +from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest + +from tribler.core.utilities.limited_ordered_dict import LimitedOrderedDict +from tribler.gui.defs import BUTTON_TYPE_NORMAL, DEFAULT_API_HOST, DEFAULT_API_PORT, DEFAULT_API_PROTOCOL +from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog +from tribler.gui.network.request.shutdown_request import ShutdownRequest +from tribler.gui.utilities import connect + +if TYPE_CHECKING: + from tribler.gui.network.request.request import Request + + +class RequestManager(QNetworkAccessManager): + """ + This class is responsible for all the requests made to the Tribler REST API. + All requests are asynchronous so the caller object should keep track of response (QNetworkReply) object. A finished + pyqt signal is fired when the response data is ready. + """ + + window = None + + def __init__(self, limit: int = 50, timeout_interval: int = 15): + QNetworkAccessManager.__init__(self) + self.logger = logging.getLogger(self.__class__.__name__) + + self.active_requests = {} + self.performed_requests: Dict[Request, int] = LimitedOrderedDict(limit=200) + + self.protocol = DEFAULT_API_PROTOCOL + self.host = DEFAULT_API_HOST + self.port = DEFAULT_API_PORT + self.key = b"" + self.limit = limit + self.timeout_interval = timeout_interval + + def add(self, request: Request): + if len(self.active_requests) > self.limit: + self._drop_timed_out_requests() + + self.active_requests[request] = request + self.performed_requests[request] = 0 + request.manager = self + request.url = self._get_base_url() + request.endpoint + request.url += f'?{self._urlencode(request.url_params)}' if request.url_params else '' + self.logger.info(f'Request: {request}') + + qt_request = QNetworkRequest(QUrl(request.url)) + qt_request.setPriority(request.priority) + qt_request.setHeader(QNetworkRequest.ContentTypeHeader, 'application/x-www-form-urlencoded') + qt_request.setRawHeader(b'X-Api-Key', self.key.encode('ascii')) + + buf = QBuffer() + if request.data: + buf.setData(request.data) + buf.open(QIODevice.ReadOnly) + + # A workaround for Qt5 bug. See https://github.com/Tribler/tribler/issues/7018 + self.setNetworkAccessible(QNetworkAccessManager.Accessible) + + request.reply = self.sendCustomRequest(qt_request, request.method.encode("utf8"), buf) + buf.setParent(request.reply) + + connect(request.reply.finished, request._on_finished) # pylint: disable=protected-access + + def remove(self, request: Request): + self.active_requests.pop(request, None) + + if request.reply: + request.reply.deleteLater() + request.reply = None + + def update(self, request: Request, status: int): + self.logger.debug(f'Update {request}: {status}') + + self.performed_requests[request] = status + + def show_error(self, request: Request, data: Dict) -> str: + text = self.get_message_from_error(data) + if self.window.core_manager.shutting_down: + return '' + + text = f'An error occurred during the request "{request}":\n\n{text}' + error_dialog = ConfirmationDialog(self.window, "Request error", text, [('CLOSE', BUTTON_TYPE_NORMAL)]) + + def on_close(_): + error_dialog.close_dialog() + + connect(error_dialog.button_clicked, on_close) + error_dialog.show() + return text + + def _get_base_url(self) -> str: + return f'{self.protocol}://{self.host}:{self.port}/' + + @staticmethod + def get_message_from_error(d: Dict) -> str: + error = d.get('error', {}) + if isinstance(error, str): + return error + + if message := error.get('message'): + return message + + return json.dumps(d) + + def clear(self, skip_shutdown_request=True): + for req in list(self.active_requests.values()): + if skip_shutdown_request and isinstance(req, ShutdownRequest): + continue + req.cancel_request() + + def _drop_timed_out_requests(self): + for req in list(self.active_requests.values()): + is_time_to_cancel = time() - req.time > self.timeout_interval + if is_time_to_cancel: + req.cancel() + + def _urlencode(self, data): + # Convert all keys and values in the data to utf-8 unicode strings + utf8_items = [] + for key, value in data.items(): + if isinstance(value, list): + utf8_items.extend([self._urlencode_single(key, list_item) for list_item in value if value]) + else: + utf8_items.append(self._urlencode_single(key, value)) + + data = "&".join(utf8_items) + return data + + @staticmethod + def _urlencode_single(key, value): + utf8_key = quote_plus(str(key).encode('utf-8')) + # Convert bool values to ints + if isinstance(value, bool): + value = int(value) + utf8_value = quote_plus(str(value).encode('utf-8')) + return f"{utf8_key}={utf8_value}" + + +# Request manager singleton. +request_manager = RequestManager() diff --git a/src/tribler/gui/network/tests/test_request_manager.py b/src/tribler/gui/network/tests/test_request_manager.py new file mode 100644 index 00000000000..f503abafac2 --- /dev/null +++ b/src/tribler/gui/network/tests/test_request_manager.py @@ -0,0 +1,44 @@ +import pytest + +from tribler.gui.network.request_manager import RequestManager + + +# pylint: disable=protected-access, redefined-outer-name + + +@pytest.fixture +def request_manager(): + return RequestManager() + + +def test_get_base_string(request_manager: RequestManager): + assert request_manager._get_base_url() == 'http://localhost:20100/' + + +def test_get_message_from_error_string(request_manager: RequestManager): + message = request_manager.get_message_from_error( + { + 'error': 'message' + } + ) + assert message == 'message' + + +def test_get_message_from_error_dict_string(request_manager: RequestManager): + message = request_manager.get_message_from_error( + { + 'error': { + 'message': 'error message' + } + } + ) + assert message == 'error message' + + +def test_get_message_from_error_any_dict(request_manager: RequestManager): + message = request_manager.get_message_from_error( + { + 'key': 'value' + } + ) + assert message == '{"key": "value"}' diff --git a/src/tribler/gui/tribler_request_manager.py b/src/tribler/gui/tribler_request_manager.py deleted file mode 100644 index 561649d6d39..00000000000 --- a/src/tribler/gui/tribler_request_manager.py +++ /dev/null @@ -1,275 +0,0 @@ -import json -import logging -from collections import deque -from time import time -from urllib.parse import quote_plus - -from PyQt5.QtCore import QBuffer, QIODevice, QObject, QUrl, pyqtSignal -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest - -from tribler.gui.defs import BUTTON_TYPE_NORMAL, DEFAULT_API_HOST, DEFAULT_API_PORT, DEFAULT_API_PROTOCOL -from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog -from tribler.gui.utilities import connect - - -def tribler_urlencode(data): - # Convert all keys and values in the data to utf-8 unicode strings - utf8_items = [] - for key, value in data.items(): - if isinstance(value, list): - utf8_items.extend([tribler_urlencode_single(key, list_item) for list_item in value if value]) - else: - utf8_items.append(tribler_urlencode_single(key, value)) - - data = "&".join(utf8_items) - return data - - -def tribler_urlencode_single(key, value): - utf8_key = quote_plus(str(key).encode('utf-8')) - # Convert bool values to ints - if isinstance(value, bool): - value = int(value) - utf8_value = quote_plus(str(value).encode('utf-8')) - return f"{utf8_key}={utf8_value}" - - -performed_requests = deque(maxlen=200) - - -class TriblerRequestManager(QNetworkAccessManager): - """ - This class is responsible for all the requests made to the Tribler REST API. - All requests are asynchronous so the caller object should keep track of response (QNetworkReply) object. A finished - pyqt signal is fired when the response data is ready. - """ - - window = None - - max_in_flight = 50 - request_timeout_interval = 15 # seconds - - def __init__(self): - QNetworkAccessManager.__init__(self) - self.requests_in_flight = {} - self.protocol = DEFAULT_API_PROTOCOL - self.host = DEFAULT_API_HOST - self.port = DEFAULT_API_PORT - self.key = b"" - - def get_base_url(self): - return "%s://%s:%d/" % (self.protocol, self.host, self.port) - - @staticmethod - def get_message_from_error(error): - return_error = None - if isinstance(error['error'], str): - return_error = error['error'] - elif 'message' in error['error']: - return_error = error['error']['message'] - - if not return_error: - return json.dumps(error) # Just print the json object - return return_error - - def show_error(self, error_text): - main_text = f"An error occurred during the request:\n\n{error_text}" - error_dialog = ConfirmationDialog( - TriblerRequestManager.window, "Request error", main_text, [('CLOSE', BUTTON_TYPE_NORMAL)] - ) - - def on_close(checked): - error_dialog.close_dialog() - - connect(error_dialog.button_clicked, on_close) - error_dialog.show() - - def clear(self, skip_shutdown_request=True): - for req in list(self.requests_in_flight.values()): - if skip_shutdown_request and isinstance(req, ShutdownRequest): - continue - req.cancel_request() - - def evict_timed_out_requests(self): - t = time() - for req in list(self.requests_in_flight.values()): - if t - req.time > self.request_timeout_interval: - req.cancel_request() - - def add_request(self, request): - if len(self.requests_in_flight) > self.max_in_flight: - self.evict_timed_out_requests() - - self.requests_in_flight[id(request)] = request - log = [request, 0] - performed_requests.append(log) - - # qt_request is managed by QNetworkAccessManager, so we don't have to - qt_request = QNetworkRequest(QUrl(request.url)) - qt_request.setPriority(request.priority) - qt_request.setHeader(QNetworkRequest.ContentTypeHeader, request.content_type_header) - qt_request.setRawHeader(b'X-Api-Key', self.key.encode('ascii')) - - buf = QBuffer() - if request.raw_data: - buf.setData(request.raw_data) - buf.open(QIODevice.ReadOnly) - - # A workaround for Qt5 bug. See https://github.com/Tribler/tribler/issues/7018 - self.setNetworkAccessible(QNetworkAccessManager.Accessible) - - request.reply = self.sendCustomRequest(qt_request, request.method.encode("utf8"), buf) - buf.setParent(request.reply) - - connect(request.reply.finished, lambda: request.on_finished(request)) - - -# Request manager singleton. -request_manager = TriblerRequestManager() - - -class TriblerNetworkRequest(QObject): - # This signal is called if we receive some real reply from the request - # and if the user defined a callback to call on the received data. - # We implement the callback as a signal call and not as a direct callback - # because we want the request object be deleted independent of what happens - # during the callback call. - received_json = pyqtSignal(object) - request_finished = pyqtSignal(object, int) - received_error = pyqtSignal(int) - - def __init__( - self, - endpoint, - reply_callback, - url_params=None, - data=None, - raw_data=None, - method='GET', - capture_core_errors=True, - priority=QNetworkRequest.NormalPriority, - on_cancel=lambda: None, - decode_json_response=True, - on_error_callback=None, - include_header_in_response=None, - content_type_header="application/x-www-form-urlencoded", - ): - QObject.__init__(self) - - # data and raw_data should never come together - if data and raw_data: - raise Exception - - if endpoint.startswith("http:") or endpoint.startswith("https:"): - url = endpoint - else: - url = request_manager.get_base_url() + endpoint - url += ("?" + tribler_urlencode(url_params)) if url_params else "" - - self.decode_json_response = decode_json_response - self.time = time() - self.url = url - self.priority = priority - self.on_cancel = on_cancel - self.method = method - self.capture_core_errors = capture_core_errors - self.include_header_in_response = include_header_in_response - self.content_type_header = content_type_header - if data: - raw_data = json.dumps(data) - self.raw_data = raw_data if (issubclass(type(raw_data), bytes) or raw_data is None) else raw_data.encode('utf8') - self.reply_callback = reply_callback - if self.reply_callback: - connect(self.received_json, self.reply_callback) - - self.on_error_callback = on_error_callback - if on_error_callback is not None: - connect(self.received_error, on_error_callback) - self.reply = None # to hold the associated QNetworkReply object - - # Pass the newly created object to the manager singleton, so the object can be dispatched immediately - request_manager.add_request(self) - - def on_finished(self, request): - if self.reply is None: - # Fix for rare race condition where reply is overwritten by e.g. previous call to destruct - self.destruct() - return - - status_code = self.reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) - - # Set the status code in the performed requests log - for item in performed_requests: - if item[0] == request: - item[1] = status_code - break - - try: - if not self.reply.isOpen() or not status_code: - self.received_error.emit(self.reply.error()) - return - - data = self.reply.readAll() - if not self.decode_json_response: - if self.include_header_in_response is not None: - header = self.reply.header(self.include_header_in_response) - self.received_json.emit((bytes(data), header)) - else: - self.received_json.emit(data) - return - json_result = json.loads(bytes(data)) - if ( - 'error' in json_result - and self.capture_core_errors - and not TriblerRequestManager.window.core_manager.shutting_down - ): - # TODO: Report REST API errors to Sentry - logging.error(f'REST API error: {json_result}') - request_manager.show_error(TriblerRequestManager.get_message_from_error(json_result)) - else: - self.received_json.emit(json_result) - except ValueError: - self.received_error.emit(self.reply.error()) - logging.error(f"No json object could be decoded from data: {data}") - finally: - self.destruct() # the request object should be properly destroyed no matter what - - def destruct(self): - """ - Call Qt deletion procedure for the object and its member objects - and remove the object from the request_manager's list of requests in flight - """ - if self.reply is not None: - self.reply.deleteLater() - self.reply = None - try: - request_manager.requests_in_flight.pop(id(self)) - except KeyError: - pass - - def cancel_request(self): - """ - Cancel the request by aborting the reply handle and calling on_cancel if available. - """ - logging.warning(f'Request from GUI to Core was canceled: {self.url}') - if self.reply: - self.reply.abort() - self.on_cancel() - self.destruct() - - -class TriblerFileDownloadRequest(TriblerNetworkRequest): - def __init__(self, endpoint, read_callback): - TriblerNetworkRequest.__init__( - self, - endpoint, - read_callback, - method="GET", - priority=QNetworkRequest.LowPriority, - decode_json_response=False, - ) - - -class ShutdownRequest(TriblerNetworkRequest): - def __init__(self, *args, **kwargs): - super().__init__("shutdown", *args, **kwargs, method="PUT", priority=QNetworkRequest.HighPriority) diff --git a/src/tribler/gui/tribler_window.py b/src/tribler/gui/tribler_window.py index e76b5ff610e..8e2a19a8982 100644 --- a/src/tribler/gui/tribler_window.py +++ b/src/tribler/gui/tribler_window.py @@ -85,12 +85,12 @@ from tribler.gui.error_handler import ErrorHandler from tribler.gui.event_request_manager import EventRequestManager from tribler.gui.exceptions import TriblerGuiTestException -from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import ( - TriblerNetworkRequest, - TriblerRequestManager, +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import ( + RequestManager, request_manager, ) +from tribler.gui.tribler_action_menu import TriblerActionMenu from tribler.gui.upgrade_manager import UpgradeManager from tribler.gui.utilities import ( connect, @@ -233,7 +233,7 @@ def __init__( sys.excepthook = self.error_handler.gui_error uic.loadUi(get_ui_file_path('mainwindow.ui'), self) - TriblerRequestManager.window = self + RequestManager.window = self self.tribler_status_bar.hide() self.token_balance_widget.mouseReleaseEvent = self.on_token_balance_click @@ -401,10 +401,11 @@ def on_test_tribler_gui_exception(self, *_): raise TriblerGuiTestException("Tribler GUI Test Exception") def on_test_tribler_core_exception(self, *_): - def dummy_callback(_): - pass - - TriblerNetworkRequest("/debug/core_test_exception", dummy_callback, method='POST') + request = Request( + endpoint="/debug/core_test_exception", + method=Request.POST + ) + request_manager.add(request) def restore_window_geometry(self): screen_geometry: QRect = QApplication.desktop().availableGeometry() @@ -446,12 +447,13 @@ def update_channels_state(_): self.add_to_channel_dialog.clear_channels_tree() def create_channel_callback(channel_name): - TriblerNetworkRequest( - "channels/mychannel/0/channels", - update_channels_state, - method='POST', - raw_data=json.dumps({"name": channel_name}) if channel_name else None, + request = Request( + endpoint="channels/mychannel/0/channels", + on_finish=update_channels_state, + method=Request.POST, + data=json.dumps({"name": channel_name}) if channel_name else None, ) + request_manager.add(request) NewChannelDialog(self, create_channel_callback) @@ -683,16 +685,19 @@ def perform_start_download_request( anon_hops = int(self.tribler_settings['download_defaults']['number_hops']) if anon_download else 0 safe_seeding = 1 if safe_seeding else 0 - post_data = { - "uri": uri, - "anon_hops": anon_hops, - "safe_seeding": safe_seeding, - "destination": destination, - "selected_files": selected_files, - } - TriblerNetworkRequest( - "downloads", callback if callback else self.on_download_added, method='PUT', data=post_data + request = Request( + endpoint="downloads", + on_finish=callback if callback else self.on_download_added, + method=Request.PUT, + data={ + "uri": uri, + "anon_hops": anon_hops, + "safe_seeding": safe_seeding, + "destination": destination, + "selected_files": selected_files, + } ) + request_manager.add(request) self.update_recent_download_locations(destination) @@ -711,12 +716,14 @@ def on_add_button_pressed(channel_id): post_data['uri'] = uri if post_data: - TriblerNetworkRequest( - f"channels/mychannel/{channel_id}/torrents", - lambda _: self.tray_show_message(tr("Channel update"), tr("Torrent(s) added to your channel")), - method='PUT', + request = Request( + endpoint=f"channels/mychannel/{channel_id}/torrents", + on_finish=lambda _: self.tray_show_message(tr("Channel update"), + tr("Torrent(s) added to your channel")), + method=Request.PUT, data=post_data, ) + request_manager.add(request) self.window().add_to_channel_dialog.show_dialog(on_add_button_pressed, confirm_button_text="Add torrent") @@ -725,12 +732,14 @@ def on_add_button_pressed(channel_id): post_data = {'torrent': torrent_data} if post_data: - TriblerNetworkRequest( - f"channels/mychannel/{channel_id}/torrents", - lambda _: self.tray_show_message(tr("Channel update"), tr("Torrent(s) added to your channel")), + request = Request( + endpoint=f"channels/mychannel/{channel_id}/torrents", + on_finish=lambda _: self.tray_show_message(tr("Channel update"), + tr("Torrent(s) added to your channel")), method='PUT', data=post_data, ) + request_manager.add(request) self.window().add_to_channel_dialog.show_dialog(on_add_button_pressed, confirm_button_text="Add torrent") @@ -741,7 +750,14 @@ def on_search_text_change(self, text): # We do not want to bother the database on petty 1-character queries if len(text) < 2: return - TriblerNetworkRequest("search/completions", self.on_received_search_completions, url_params={'q': text}) + request = Request( + endpoint="search/completions", + on_finish=self.on_received_search_completions, + url_params={ + 'q': text + } + ) + request_manager.add(request) def on_received_search_completions(self, completions): if completions is None: @@ -754,14 +770,19 @@ def on_received_search_completions(self, completions): self.search_completion_model.setStringList(completions_list) def fetch_settings(self): - TriblerNetworkRequest("settings", self.received_settings, capture_core_errors=False) + request = Request( + endpoint="settings", + on_finish=self.received_settings, + capture_errors=False + ) + request_manager.add(request) def received_settings(self, settings): if not settings: return # If we cannot receive the settings, stop Tribler with an option to send the crash report. if 'error' in settings: - raise RuntimeError(TriblerRequestManager.get_message_from_error(settings)) + raise RuntimeError(RequestManager.get_message_from_error(settings)) # If there is any pending dialog (likely download dialog or error dialog of setting not available), # close the dialog @@ -801,7 +822,12 @@ def on_token_balance_click(self, _): self.trust_page.load_history() def load_token_balance(self): - TriblerNetworkRequest("bandwidth/statistics", self.received_bandwidth_statistics, capture_core_errors=False) + request = Request( + endpoint="bandwidth/statistics", + on_finish=self.received_bandwidth_statistics, + capture_errors=False + ) + request_manager.add(request) def received_bandwidth_statistics(self, statistics): if not statistics or "statistics" not in statistics: @@ -991,14 +1017,17 @@ def on_add_button_pressed(channel_id): show_message_box(f'"{self.chosen_dir}" is not a directory') return - TriblerNetworkRequest( - f"collections/mychannel/{channel_id}/torrents", - lambda _: self.tray_show_message( + request = Request( + endpoint=f"collections/mychannel/{channel_id}/torrents", + on_finish=lambda _: self.tray_show_message( tr("Channels update"), tr("%s added to your channel") % self.chosen_dir ), - method='PUT', - data={"torrents_dir": self.chosen_dir}, + method=Request.PUT, + data={ + "torrents_dir": self.chosen_dir + }, ) + request_manager.add(request) self.window().add_to_channel_dialog.show_dialog( on_add_button_pressed, confirm_button_text=tr("Add torrent(s)") @@ -1234,23 +1263,25 @@ def clicked_skip_conversion(self): def on_channel_subscribe(self, channel_info): patch_data = [{"public_key": channel_info['public_key'], "id": channel_info['id'], "subscribed": True}] - TriblerNetworkRequest( - "metadata", - lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]), - raw_data=json.dumps(patch_data), - method='PATCH', + request = Request( + endpoint="metadata", + on_finish=lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]), + data=json.dumps(patch_data), + method=Request.PATCH, ) + request_manager.add(request) def on_channel_unsubscribe(self, channel_info): def _on_unsubscribe_action(action): if action == 0: patch_data = [{"public_key": channel_info['public_key'], "id": channel_info['id'], "subscribed": False}] - TriblerNetworkRequest( - "metadata", - lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]), - raw_data=json.dumps(patch_data), - method='PATCH', + request = Request( + endpoint="metadata", + on_finish=lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]), + data=json.dumps(patch_data), + method=Request.PATCH, ) + request_manager.add(request) if self.dialog: self.dialog.close_dialog() self.dialog = None @@ -1272,12 +1303,13 @@ def on_channel_delete(self, channel_info): def _on_delete_action(action): if action == 0: delete_data = [{"public_key": channel_info['public_key'], "id": channel_info['id']}] - TriblerNetworkRequest( - "metadata", - lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]), - raw_data=json.dumps(delete_data), - method='DELETE', + request = Request( + endpoint="metadata", + on_finish=lambda data: self.core_manager.events_manager.node_info_updated.emit(data[0]), + data=json.dumps(delete_data), + method=Request.DELETE, ) + request_manager.add(request) if self.dialog: self.dialog.close_dialog() self.dialog = None diff --git a/src/tribler/gui/widgets/channelcontentswidget.py b/src/tribler/gui/widgets/channelcontentswidget.py index 2d150fc7413..3a44becffc0 100644 --- a/src/tribler/gui/widgets/channelcontentswidget.py +++ b/src/tribler/gui/widgets/channelcontentswidget.py @@ -4,13 +4,11 @@ from PyQt5.QtCore import QDir, QTimer, Qt, pyqtSignal from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QAction, QFileDialog - from psutil import LINUX from tribler.core.components.metadata_store.db.orm_bindings.channel_node import DIRTY_STATUSES, NEW from tribler.core.components.metadata_store.db.serialization import CHANNEL_TORRENT, COLLECTION_NODE from tribler.core.utilities.simpledefs import CHANNEL_STATE - from tribler.gui.defs import ( BUTTON_TYPE_CONFIRM, BUTTON_TYPE_NORMAL, @@ -19,9 +17,10 @@ ) from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog from tribler.gui.dialogs.new_channel_dialog import NewChannelDialog +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, disconnect, get_image_path, get_ui_file_path, tr from tribler.gui.widgets.tablecontentmodel import ( ChannelContentModel, @@ -39,7 +38,6 @@ # pylint: disable=too-many-instance-attributes, too-many-public-methods class ChannelContentsWidget(AddBreadcrumbOnShowMixin, widget_form, widget_class): - model_query_completed = pyqtSignal() def __init__(self, parent=None): @@ -133,14 +131,19 @@ def on_channel_committed(self, response): self.update_labels() def commit_channels(self, checked=False): # pylint: disable=W0613 - TriblerNetworkRequest("channels/mychannel/0/commit", self.on_channel_committed, method='POST') + request = Request( + endpoint="channels/mychannel/0/commit", + on_finish=self.on_channel_committed, + method=Request.POST + ) + request_manager.add(request) def initialize_content_page( - self, - autocommit_enabled=False, - hide_xxx=None, - controller_class=ContentTableViewController, - categories=CATEGORY_SELECTOR_FOR_SEARCH_ITEMS, + self, + autocommit_enabled=False, + hide_xxx=None, + controller_class=ContentTableViewController, + categories=CATEGORY_SELECTOR_FOR_SEARCH_ITEMS, ): if self.initialized: return @@ -252,9 +255,10 @@ def on_model_info_changed(self, changed_entries): for entry in changed_entries: dirty = dirty or entry.get('status', None) in DIRTY_STATUSES structure_changed = ( - structure_changed - or entry.get("state", None) == "Deleted" - or (entry.get("type", None) in [CHANNEL_TORRENT, COLLECTION_NODE] and entry["status"] in DIRTY_STATUSES) + structure_changed + or entry.get("state", None) == "Deleted" + or (entry.get("type", None) in [CHANNEL_TORRENT, COLLECTION_NODE] and entry[ + "status"] in DIRTY_STATUSES) ) if structure_changed: @@ -407,15 +411,15 @@ def update_navigation_breadcrumbs(self): if must_elide: channel_name = "..." breadcrumb_text = ( - slash_separator - + f'{channel_name}' - + breadcrumb_text + slash_separator + + f'{channel_name}' + + breadcrumb_text ) if must_elide: break # Remove the leftmost slash: if len(breadcrumb_text) >= len(slash_separator): - breadcrumb_text = breadcrumb_text[len(slash_separator) :] + breadcrumb_text = breadcrumb_text[len(slash_separator):] self.channel_name_label.setText(breadcrumb_text) self.channel_name_label.setTextInteractionFlags(Qt.TextBrowserInteraction) @@ -575,12 +579,14 @@ def _on_torrent_to_channel_added(self, result): self.model.reset() def _add_torrent_request(self, data): - TriblerNetworkRequest( - f'collections/mychannel/{self.model.channel_info["id"]}/torrents', - self._on_torrent_to_channel_added, - method='PUT', + channel_id = self.model.channel_info["id"] + request = Request( + endpoint=f'collections/mychannel/{channel_id}/torrents', + on_finish=self._on_torrent_to_channel_added, + method=Request.PUT, data=data, ) + request_manager.add(request) def add_torrent_to_channel(self, filename): with open(filename, "rb") as torrent_file: diff --git a/src/tribler/gui/widgets/channeldescriptionwidget.py b/src/tribler/gui/widgets/channeldescriptionwidget.py index e0bac1a0a6e..c2c078f35e7 100644 --- a/src/tribler/gui/widgets/channeldescriptionwidget.py +++ b/src/tribler/gui/widgets/channeldescriptionwidget.py @@ -3,12 +3,12 @@ from PyQt5 import QtCore, uic from PyQt5.QtCore import QDir, pyqtSignal, pyqtSlot from PyQt5.QtGui import QIcon, QImage, QPixmap -from PyQt5.QtNetwork import QNetworkRequest from PyQt5.QtWidgets import QFileDialog, QPushButton from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, get_image_path, get_ui_file_path, tr widget_form, widget_class = uic.loadUiType(get_ui_file_path('channel_description.ui')) @@ -21,7 +21,6 @@ DEFAULT_THUMBNAIL_PIXMAP = QPixmap(get_image_path('chan_thumb.png')) CREATE_THUMBNAIL_TEXT = tr("Click this to add \n channel thumbnail \n (max. 1MB JPG/PNG)") - PREVIEW_PAGE = 0 EDIT_PAGE = 1 @@ -78,7 +77,6 @@ def __init__(self, parent=None): self.description_text = None self.channel_thumbnail_bytes = None - self.channel_thumbnail_type = None self.channel_thumbnail_qimage = None self.channel_pk = None @@ -131,7 +129,6 @@ def on_start_editing(self): def on_create_description_button_clicked(self, *args): self.description_text = "" self.channel_thumbnail_bytes = None - self.channel_thumbnail_type = None self.show_description_page() self.on_start_editing() @@ -148,12 +145,15 @@ def on_save_button_clicked(self): if self.description_text is not None: descr_changed = True - TriblerNetworkRequest( - f'channels/{self.channel_pk}/{self.channel_id}/description', - self._on_description_received, - method='PUT', - data={"description_text": self.description_text}, + request = Request( + endpoint=f'channels/{self.channel_pk}/{self.channel_id}/description', + on_finish=self._on_description_received, + method=Request.PUT, + data={ + "description_text": self.description_text + }, ) + request_manager.add(request) if self.channel_thumbnail_bytes is not None: thumb_changed = True @@ -161,14 +161,14 @@ def on_save_button_clicked(self): def _on_thumbnail_updated(_): pass - TriblerNetworkRequest( - f'channels/{self.channel_pk}/{self.channel_id}/thumbnail', - _on_thumbnail_updated, - method='PUT', - raw_data=self.channel_thumbnail_bytes, - decode_json_response=False, - content_type_header=self.channel_thumbnail_type, + request = Request( + endpoint=f'channels/{self.channel_pk}/{self.channel_id}/thumbnail', + on_finish=_on_thumbnail_updated, + method=Request.PUT, + data=self.channel_thumbnail_bytes, + raw_response=True, ) + request_manager.add(request) if descr_changed or thumb_changed: self.description_changed.emit() @@ -200,7 +200,6 @@ def on_channel_thumbnail_clicked(self): return self.channel_thumbnail_bytes = data - self.channel_thumbnail_type = content_type self.update_channel_thumbnail(data, content_type) @pyqtSlot() @@ -244,13 +243,12 @@ def _on_description_received(self, result): self.description_text = None self.description_text_preview.setMarkdown("") - TriblerNetworkRequest( - f'channels/{self.channel_pk}/{self.channel_id}/thumbnail', - self._on_thumbnail_received, - method='GET', - decode_json_response=False, - include_header_in_response=QNetworkRequest.ContentTypeHeader, + request = Request( + endpoint=f'channels/{self.channel_pk}/{self.channel_id}/thumbnail', + on_finish=self._on_thumbnail_received, + raw_response=True, ) + request_manager.add(request) def set_widget_visible(self, show): self.bottom_buttons_container.setHidden(True) @@ -280,13 +278,11 @@ def _on_thumbnail_received(self, result_and_header): result, header = result_and_header if not (result and header): self.channel_thumbnail_bytes = None - self.channel_thumbnail_type = None self.channel_thumbnail.setPixmap(DEFAULT_THUMBNAIL_PIXMAP) self.set_widget_visible(self.description_text is not None) return self.channel_thumbnail_bytes = result - self.channel_thumbnail_type = header - self.update_channel_thumbnail(self.channel_thumbnail_bytes, self.channel_thumbnail_type) + self.update_channel_thumbnail(result, header) self.set_widget_visible(True) def initialize_with_channel(self, channel_pk, channel_id, edit=True): @@ -295,11 +291,11 @@ def initialize_with_channel(self, channel_pk, channel_id, edit=True): self.floating_edit_button.setHidden(not self.edit_enabled) self.channel_pk, self.channel_id = channel_pk, channel_id - TriblerNetworkRequest( - f'channels/{self.channel_pk}/{self.channel_id}/description', - self._on_description_received, - method='GET', + request = Request( + endpoint=f'channels/{self.channel_pk}/{self.channel_id}/description', + on_finish=self._on_description_received, ) + request_manager.add(request) def enable_edit(self): self.floating_edit_button.setHidden(False) diff --git a/src/tribler/gui/widgets/channelsmenulistwidget.py b/src/tribler/gui/widgets/channelsmenulistwidget.py index 5c62b6818ce..11ead5b344b 100644 --- a/src/tribler/gui/widgets/channelsmenulistwidget.py +++ b/src/tribler/gui/widgets/channelsmenulistwidget.py @@ -6,9 +6,9 @@ from tribler.core.components.metadata_store.db.serialization import CHANNEL_TORRENT from tribler.core.utilities.simpledefs import CHANNEL_STATE - +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, get_image_path, tr @@ -33,12 +33,13 @@ def setData(self, role, new_value): if role == Qt.EditRole: item = self.channel_info if item['name'] != new_value: - TriblerNetworkRequest( - f"metadata/{item['public_key']}/{item['id']}", - lambda _: None, - method='PATCH', - raw_data=json.dumps({"title": new_value}), + request = Request( + endpoint=f"metadata/{item['public_key']}/{item['id']}", + method=Request.PATCH, + data=json.dumps({"title": new_value}), ) + request_manager.add(request) + return super().setData(role, new_value) @@ -128,13 +129,21 @@ def on_query_results(self, response): item.setIcon(self.empty_image) tooltip_text = channel_info['name'] + "\n" + channel_info['state'] if channel_info.get('progress'): - tooltip_text += f" {int(float(channel_info['progress'])*100)}%" + tooltip_text += f" {int(float(channel_info['progress']) * 100)}%" item.setToolTip(tooltip_text) self.items_set = frozenset(entry_to_tuple(channel_info) for channel_info in channels) def load_channels(self): - TriblerNetworkRequest(self.base_url, self.on_query_results, url_params={"subscribed": True, "last": 1000}) + request = Request( + endpoint=self.base_url, + on_finish=self.on_query_results, + url_params={ + "subscribed": True, + "last": 1000 + } + ) + request_manager.add(request) def reload_if_necessary(self, changed_entries): # Compare the state changes in the changed entries list to our current list diff --git a/src/tribler/gui/widgets/createtorrentpage.py b/src/tribler/gui/widgets/createtorrentpage.py index 0312e15b8b2..d4c4b59c517 100644 --- a/src/tribler/gui/widgets/createtorrentpage.py +++ b/src/tribler/gui/widgets/createtorrentpage.py @@ -6,9 +6,10 @@ from tribler.gui.defs import BUTTON_TYPE_NORMAL, PAGE_EDIT_CHANNEL_TORRENTS from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, get_image_path @@ -88,9 +89,20 @@ def on_create_clicked(self, checked): name = self.window().create_torrent_name_field.text() description = self.window().create_torrent_description_field.toPlainText() - post_data = {"name": name, "description": description, "files": files_list} - url = "createtorrent?download=1" if self.window().seed_after_adding_checkbox.isChecked() else "createtorrent" - TriblerNetworkRequest(url, self.on_torrent_created, data=post_data, method='POST') + + is_seed = self.window().seed_after_adding_checkbox.isChecked() + request = Request( + endpoint='createtorrent', + on_finish=self.on_torrent_created, + data={ + "name": name, + "description": description, + "files": files_list + }, + url_params={'download': 1} if is_seed else None, + method=Request.POST + ) + request_manager.add(request) # Show creating torrent text self.window().edit_channel_create_torrent_progress_label.show() @@ -106,9 +118,15 @@ def on_torrent_created(self, result): self.add_torrent_to_channel(result['torrent']) def add_torrent_to_channel(self, torrent): - TriblerNetworkRequest( - "mychannel/torrents", self.on_torrent_to_channel_added, data={"torrent": torrent}, method='PUT' + request = Request( + endpoint="mychannel/torrents", + on_finish=self.on_torrent_to_channel_added, + data={ + "torrent": torrent + }, + method=Request.PUT ) + request_manager.add(request) def on_torrent_to_channel_added(self, result): if not result: diff --git a/src/tribler/gui/widgets/downloadsdetailstabwidget.py b/src/tribler/gui/widgets/downloadsdetailstabwidget.py index b92f15e35c3..23115bcb040 100644 --- a/src/tribler/gui/widgets/downloadsdetailstabwidget.py +++ b/src/tribler/gui/widgets/downloadsdetailstabwidget.py @@ -4,9 +4,9 @@ from PyQt5.QtWidgets import QTabWidget, QTreeWidgetItem from tribler.core.utilities.simpledefs import dlstatus_strings - from tribler.gui.defs import DLSTATUS_STOPPED_ON_ERROR, DLSTATUS_STRINGS -from tribler.gui.tribler_request_manager import TriblerNetworkRequest +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.utilities import compose_magnetlink, connect, copy_to_clipboard, format_size, format_speed, tr INCLUDED_FILES_CHANGE_DELAY = 1000 # milliseconds @@ -77,9 +77,9 @@ def update_with_download(self, download): # that's a different download. Thus, we must differ between the old one and the new one, to prevent # "caching" the previous parameters. The most reliable way to make difference is by time_added property did_change = ( - self.current_download is None - or self.current_download.get('infohash') != download.get('infohash') - or self.current_download.get('time_added') != download.get('time_added') + self.current_download is None + or self.current_download.get('infohash') != download.get('infohash') + or self.current_download.get('time_added') != download.get('time_added') ) self.current_download = download # When we switch to another download, we want to fixate the changes user did to selected files. @@ -199,10 +199,14 @@ def set_included_files(self): if not self.current_download: return included_list = self.window().download_files_list.get_selected_files_indexes() - post_data = {"selected_files": included_list} - TriblerNetworkRequest( - f"downloads/{self.current_download['infohash']}", lambda _: None, method='PATCH', data=post_data + request = Request( + endpoint=f"downloads/{self.current_download['infohash']}", + method=Request.PATCH, + data={ + "selected_files": included_list + } ) + request_manager.add(request) def on_copy_magnet_clicked(self, checked): trackers = [ diff --git a/src/tribler/gui/widgets/downloadspage.py b/src/tribler/gui/widgets/downloadspage.py index 0cb5a7efed8..78cdf6c2e82 100644 --- a/src/tribler/gui/widgets/downloadspage.py +++ b/src/tribler/gui/widgets/downloadspage.py @@ -1,7 +1,7 @@ import logging import os import time -from typing import Optional +from typing import Optional, Tuple from PyQt5.QtCore import QTimer, QUrl, Qt, pyqtSignal from PyQt5.QtGui import QDesktopServices @@ -27,9 +27,11 @@ DOWNLOADS_FILTER_INACTIVE, ) from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog +from tribler.gui.network.request.file_download_request import FileDownloadRequest +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import TriblerFileDownloadRequest, TriblerNetworkRequest from tribler.gui.utilities import compose_magnetlink, connect, format_speed, tr from tribler.gui.widgets.downloadwidgetitem import DownloadWidgetItem, LoadingDownloadWidgetItem from tribler.gui.widgets.loading_list_item import LoadingListItem @@ -124,7 +126,7 @@ def schedule_downloads_timer(self, now=False): def on_downloads_request_timeout(self): if self.rest_request: - self.rest_request.cancel_request() + self.rest_request.cancel() self.schedule_downloads_timer() def stop_loading_downloads(self): @@ -145,8 +147,13 @@ def load_downloads(self): self.downloads_last_update = time.time() priority = QNetworkRequest.LowPriority if not isactive else QNetworkRequest.HighPriority if self.rest_request: - self.rest_request.cancel_request() - self.rest_request = TriblerNetworkRequest(url, self.on_received_downloads, priority=priority) + self.rest_request.cancel() + self.rest_request = Request( + endpoint=url, + on_finish=self.on_received_downloads, + priority=priority + ) + request_manager.add(self.rest_request) def on_received_downloads(self, downloads): if not downloads or "downloads" not in downloads: @@ -202,8 +209,8 @@ def on_received_downloads(self, downloads): download_infohashes.add(download["infohash"]) if ( - self.window().download_details_widget.current_download is not None - and self.window().download_details_widget.current_download["infohash"] == download["infohash"] + self.window().download_details_widget.current_download is not None + and self.window().download_details_widget.current_download["infohash"] == download["infohash"] ): self.window().download_details_widget.update_with_download(download) @@ -304,9 +311,15 @@ def on_download_item_clicked(self): def on_start_download_clicked(self, checked): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] - TriblerNetworkRequest( - f"downloads/{infohash}", self.on_download_resumed, method='PATCH', data={"state": "resume"} + request = Request( + endpoint=f"downloads/{infohash}", + on_finish=self.on_download_resumed, + method=Request.PATCH, + data={ + "state": "resume" + } ) + request_manager.add(request) def on_download_resumed(self, json_result): if json_result and 'modified' in json_result: @@ -319,9 +332,15 @@ def on_download_resumed(self, json_result): def on_stop_download_clicked(self, checked): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] - TriblerNetworkRequest( - f"downloads/{infohash}", self.on_download_stopped, method='PATCH', data={"state": "stop"} + request = Request( + endpoint=f"downloads/{infohash}", + on_finish=self.on_download_stopped, + method=Request.PATCH, + data={ + "state": "stop" + } ) + request_manager.add(request) def on_download_stopped(self, json_result): if json_result and "modified" in json_result: @@ -353,12 +372,15 @@ def on_remove_download_dialog(self, action): if current_download and current_download.get(infohash) == infohash: self.window().download_details_widget.current_download = None - TriblerNetworkRequest( - f"downloads/{infohash}", - self.on_download_removed, - method='DELETE', - data={"remove_data": bool(action)}, + request = Request( + endpoint=f"downloads/{infohash}", + on_finish=self.on_download_removed, + method=Request.DELETE, + data={ + "remove_data": bool(action) + }, ) + request_manager.add(request) if self.dialog: self.dialog.close_dialog() self.dialog = None @@ -374,9 +396,15 @@ def on_download_removed(self, json_result): def on_force_recheck_download(self, checked): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] - TriblerNetworkRequest( - f"downloads/{infohash}", self.on_forced_recheck, method='PATCH', data={"state": "recheck"} + request = Request( + endpoint=f"downloads/{infohash}", + on_finish=self.on_forced_recheck, + method=Request.PATCH, + data={ + "state": "recheck" + } ) + request_manager.add(request) def on_forced_recheck(self, result): if result and "modified" in result: @@ -392,9 +420,15 @@ def on_change_anonymity(self, result): def change_anonymity(self, hops): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] - TriblerNetworkRequest( - f"downloads/{infohash}", self.on_change_anonymity, method='PATCH', data={"anon_hops": hops} + request = Request( + endpoint=f"downloads/{infohash}", + on_finish=self.on_change_anonymity, + method=Request.PATCH, + data={ + "anon_hops": hops + } ) + request_manager.add(request) def on_explore_files(self, checked): # ACHTUNG! To whomever might stumble upon here intending to debug the case @@ -425,12 +459,13 @@ def on_move_files(self, checked): data = {"state": "move_storage", "dest_dir": dest_dir} - TriblerNetworkRequest( - f"downloads/{_infohash}", - lambda res: self.on_files_moved(res, _name, dest_dir), + request = Request( + endpoint=f"downloads/{_infohash}", + on_finish=lambda res: self.on_files_moved(res, _name, dest_dir), data=data, - method='PATCH', + method=Request.PATCH, ) + request_manager.add(request) def on_files_moved(self, response, name, dest_dir): if "modified" in response and response["modified"]: @@ -459,12 +494,16 @@ def on_export_download(self, checked): self.dialog.show() def on_export_download_dialog_done(self, action): + def on_finish(result: Tuple): + data, _ = result + self.on_export_download_request_done(filename, data) + selected_item = self.selected_items[:1] if action == 0 and selected_item: filename = self.dialog.dialog_widget.dialog_input.text() - TriblerFileDownloadRequest( + FileDownloadRequest( f"downloads/{selected_item[0].download_info['infohash']}/torrent", - lambda data: self.on_export_download_request_done(filename, data), + on_finish, ) self.dialog.close_dialog() @@ -492,14 +531,17 @@ def on_add_button_pressed(channel_id): for selected_item in self.selected_items: infohash = selected_item.download_info["infohash"] name = selected_item.download_info["name"] - TriblerNetworkRequest( - f"channels/mychannel/{channel_id}/torrents", - lambda _: self.window().tray_show_message( + request = Request( + endpoint=f"channels/mychannel/{channel_id}/torrents", + on_finish=lambda _: self.window().tray_show_message( tr("Channel update"), tr("Torrent(s) added to your channel") ), - method='PUT', - data={"uri": compose_magnetlink(infohash, name=name)}, + method=Request.PUT, + data={ + "uri": compose_magnetlink(infohash, name=name) + }, ) + request_manager.add(request) self.window().add_to_channel_dialog.show_dialog(on_add_button_pressed, confirm_button_text=tr("Add torrent(s)")) diff --git a/src/tribler/gui/widgets/lazytableview.py b/src/tribler/gui/widgets/lazytableview.py index 61e3c3dd304..cfa7c46afee 100644 --- a/src/tribler/gui/widgets/lazytableview.py +++ b/src/tribler/gui/widgets/lazytableview.py @@ -11,7 +11,8 @@ from tribler.gui.defs import COMMIT_STATUS_COMMITTED from tribler.gui.dialogs.editmetadatadialog import EditMetadataDialog -from tribler.gui.tribler_request_manager import TriblerNetworkRequest +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.utilities import connect, data_item2uri, get_image_path, index2uri from tribler.gui.widgets.tablecontentdelegate import TriblerContentDelegate from tribler.gui.widgets.tablecontentmodel import Column, EXPANDING @@ -281,9 +282,10 @@ def on_metadata_edited(self, index, statements: List[Dict]): def save_edited_metadata(self, index: QModelIndex, statements: List[Dict]): data_item = self.model().data_items[index.row()] - TriblerNetworkRequest( - f"knowledge/{data_item['infohash']}", - lambda _, ind=index, stmts=statements: self.on_metadata_edited(ind, statements), - raw_data=json.dumps({"statements": statements}), - method='PATCH', + request = Request( + endpoint=f"knowledge/{data_item['infohash']}", + on_finish=lambda _, ind=index, stmts=statements: self.on_metadata_edited(ind, statements), + data=json.dumps({"statements": statements}), + method=Request.PATCH, ) + request_manager.add(request) diff --git a/src/tribler/gui/widgets/searchresultswidget.py b/src/tribler/gui/widgets/searchresultswidget.py index 5eb972d0696..d8d007454e9 100644 --- a/src/tribler/gui/widgets/searchresultswidget.py +++ b/src/tribler/gui/widgets/searchresultswidget.py @@ -5,11 +5,11 @@ from PyQt5 import uic -from tribler.core.components.metadata_store.db.serialization import CHANNEL_TORRENT, COLLECTION_NODE, REGULAR_TORRENT +from tribler.core.components.metadata_store.db.serialization import REGULAR_TORRENT from tribler.core.utilities.utilities import Query, to_fts_query - +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, get_ui_file_path, tr from tribler.gui.widgets.tablecontentmodel import SearchResultsModel @@ -24,11 +24,11 @@ def format_search_loading_label(search_request): } return ( - tr( - "Remote responses: %(num_complete_peers)i / %(total_peers)i" - "\nNew remote results received: %(num_remote_results)i" - ) - % data + tr( + "Remote responses: %(num_complete_peers)i / %(total_peers)i" + "\nNew remote results received: %(num_remote_results)i" + ) + % data ) @@ -74,9 +74,9 @@ def has_results(self): def check_can_show(self, query): if ( - self.last_search_query == query - and self.last_search_time is not None - and time.time() - self.last_search_time < 1 + self.last_search_query == query + and self.last_search_time is not None + and time.time() - self.last_search_time < 1 ): self._logger.info("Same search query already sent within 500ms so dropping this one") return False @@ -118,7 +118,13 @@ def register_request(response): params = {'txt_filter': fts_query, 'hide_xxx': self.hide_xxx, 'tags': list(query.tags), 'metadata_type': REGULAR_TORRENT, 'exclude_deleted': True} - TriblerNetworkRequest('remote_query', register_request, method="PUT", url_params=params) + request = Request( + endpoint='remote_query', + on_finish=register_request, + method=Request.PUT, + url_params=params + ) + request_manager.add(request) return True diff --git a/src/tribler/gui/widgets/settingspage.py b/src/tribler/gui/widgets/settingspage.py index 2f8fe25efa3..e2ba3856f82 100644 --- a/src/tribler/gui/widgets/settingspage.py +++ b/src/tribler/gui/widgets/settingspage.py @@ -1,8 +1,5 @@ import json import logging -import os -import shutil -from typing import List from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QCheckBox, QFileDialog, QMessageBox, QSizePolicy, QWidget @@ -21,8 +18,9 @@ PAGE_SETTINGS_SEEDING, ) from tribler.gui.dialogs.confirmationdialog import ConfirmationDialog +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import ( AVAILABLE_TRANSLATIONS, connect, @@ -345,7 +343,11 @@ def load_settings(self): self.window().settings_stacked_widget.hide() self.window().settings_tab.hide() - TriblerNetworkRequest("settings", self.initialize_with_settings) + request = Request( + endpoint="settings", + on_finish=self.initialize_with_settings + ) + request_manager.add(request) def clicked_tab_button(self, tab_button_name): if tab_button_name == "settings_general_button": @@ -534,7 +536,12 @@ def save_settings(self, checked): self.window().update_recent_download_locations(settings_data['download_defaults']['saveas']) self.settings = settings_data - TriblerNetworkRequest("settings", self.on_settings_saved, method='POST', raw_data=json.dumps(settings_data)) + request = Request( + endpoint="settings", + on_finish=self.on_settings_saved, + method=Request.POST, + data=json.dumps(settings_data)) + request_manager.add(request) def save_language_selection(self): ind = self.window().language_selector.currentIndex() diff --git a/src/tribler/gui/widgets/tablecontentmodel.py b/src/tribler/gui/widgets/tablecontentmodel.py index e33c62d9cef..bb2d8b174e4 100644 --- a/src/tribler/gui/widgets/tablecontentmodel.py +++ b/src/tribler/gui/widgets/tablecontentmodel.py @@ -15,9 +15,9 @@ from tribler.core.utilities.search_utils import item_rank from tribler.core.utilities.simpledefs import CHANNELS_VIEW_UUID, CHANNEL_STATE from tribler.core.utilities.utilities import to_fts_query - from tribler.gui.defs import BITTORRENT_BIRTHDAY, COMMIT_STATUS_TODELETE, HEALTH_CHECKING -from tribler.gui.tribler_request_manager import TriblerNetworkRequest +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.utilities import connect, format_size, format_votes, get_votes_rating_description, pretty_date, tr EXPANDING = 0 @@ -55,16 +55,20 @@ def define_columns(): # fmt:off # pylint: disable=line-too-long columns_dict = { - Column.ACTIONS: d('', "", width=60, sortable=False), - Column.CATEGORY: d('category', "", width=30, tooltip_filter=lambda data: data), - Column.NAME: d('name', tr("Name"), width=EXPANDING), - Column.SIZE: d('size', tr("Size"), width=90, display_filter=lambda data: (format_size(float(data)) if data != "" else "")), - Column.HEALTH: d('health', tr("Health"), width=120, tooltip_filter=lambda data: f"{data}" + ('' if data == HEALTH_CHECKING else '\n(Click to recheck)'),), - Column.UPDATED: d('updated', tr("Updated"), width=120, display_filter=lambda timestamp: pretty_date(timestamp) if timestamp and timestamp > BITTORRENT_BIRTHDAY else "",), - Column.VOTES: d('votes', tr("Popularity"), width=120, display_filter=format_votes, tooltip_filter=lambda data: get_votes_rating_description(data) if data is not None else None,), - Column.STATUS: d('status', "", sortable=False), - Column.STATE: d('state', "", width=80, tooltip_filter=lambda data: data, sortable=False), - Column.TORRENTS: d('torrents', tr("Torrents"), width=90), + Column.ACTIONS: d('', "", width=60, sortable=False), + Column.CATEGORY: d('category', "", width=30, tooltip_filter=lambda data: data), + Column.NAME: d('name', tr("Name"), width=EXPANDING), + Column.SIZE: d('size', tr("Size"), width=90, + display_filter=lambda data: (format_size(float(data)) if data != "" else "")), + Column.HEALTH: d('health', tr("Health"), width=120, tooltip_filter=lambda data: f"{data}" + ( + '' if data == HEALTH_CHECKING else '\n(Click to recheck)'), ), + Column.UPDATED: d('updated', tr("Updated"), width=120, display_filter=lambda timestamp: pretty_date( + timestamp) if timestamp and timestamp > BITTORRENT_BIRTHDAY else "", ), + Column.VOTES: d('votes', tr("Popularity"), width=120, display_filter=format_votes, + tooltip_filter=lambda data: get_votes_rating_description(data) if data is not None else None, ), + Column.STATUS: d('status', "", sortable=False), + Column.STATE: d('state', "", width=80, tooltip_filter=lambda data: data, sortable=False), + Column.TORRENTS: d('torrents', tr("Torrents"), width=90), Column.SUBSCRIBED: d('subscribed', tr("Subscribed"), width=95), } # pylint: enable=line-too-long @@ -248,7 +252,7 @@ def add_items(self, new_items, on_top=False, remote=False): torrents += new_torrents non_torrents += new_non_torrents - torrents.sort(key = lambda item: item['rank'], reverse=True) + torrents.sort(key=lambda item: item['rank'], reverse=True) new_data_items = non_torrents + torrents new_item_uid_map = {} @@ -359,7 +363,11 @@ def perform_query(self, **kwargs): kwargs.update({"hide_xxx": self.hide_xxx}) rest_endpoint_url = kwargs.pop("rest_endpoint_url") if "rest_endpoint_url" in kwargs else self.endpoint_url self._logger.info(f'Request to "{rest_endpoint_url}":{kwargs}') - TriblerNetworkRequest(rest_endpoint_url, self.on_query_results, url_params=kwargs) + request = Request( + endpoint=rest_endpoint_url, + on_finish=self.on_query_results, + url_params=kwargs) + request_manager.add(request) def on_query_results(self, response, remote=False, on_top=False): """ @@ -404,15 +412,15 @@ class ChannelContentModel(RemoteTableModel): columns_shown = (Column.ACTIONS, Column.CATEGORY, Column.NAME, Column.SIZE, Column.HEALTH, Column.UPDATED) def __init__( - self, - channel_info=None, - hide_xxx=None, - exclude_deleted=None, - subscribed_only=None, - endpoint_url=None, - text_filter='', - tags=None, - type_filter=None, + self, + channel_info=None, + hide_xxx=None, + exclude_deleted=None, + subscribed_only=None, + endpoint_url=None, + text_filter='', + tags=None, + type_filter=None, ): RemoteTableModel.__init__(self, parent=None) @@ -492,10 +500,10 @@ def item_txt(self, index, role, is_editing: bool = False): # Print number of torrents in the channel for channel rows in the "size" column if ( - column_type == Column.SIZE - and "torrents" not in self.columns - and "torrents" in item - and item["type"] in (CHANNEL_TORRENT, COLLECTION_NODE, SNIPPET) + column_type == Column.SIZE + and "torrents" not in self.columns + and "torrents" in item + and item["type"] in (CHANNEL_TORRENT, COLLECTION_NODE, SNIPPET) ): if item["type"] == SNIPPET: return "" @@ -544,8 +552,8 @@ def update_node_info(self, update_dict): """ if ( - self.channel_info.get("public_key") == update_dict.get("public_key") is not None - and self.channel_info.get("id") == update_dict.get("id") is not None + self.channel_info.get("public_key") == update_dict.get("public_key") is not None + and self.channel_info.get("id") == update_dict.get("id") is not None ): self.channel_info.update(**update_dict) self.info_changed.emit([]) @@ -605,12 +613,13 @@ def on_row_update_results(response): data_item_dict.update(response) self.info_changed.emit([data_item_dict]) - TriblerNetworkRequest( - f"metadata/{item['public_key']}/{item['id']}", - on_row_update_results, - method='PATCH', - raw_data=json.dumps({attribute_name: new_value}), + request = Request( + endpoint=f"metadata/{item['public_key']}/{item['id']}", + on_finish=on_row_update_results, + method=Request.PATCH, + data=json.dumps({attribute_name: new_value}), ) + request_manager.add(request) # ACHTUNG: instead of reloading the whole row from DB, this line just changes the displayed value! self.data_items[index.row()][attribute_name] = new_value @@ -737,21 +746,30 @@ def delete_rows(self, rows): # button, by the moment the request callback triggers some actions on the model, # QT could have already deleted the underlying model object, which will result in # "wrapped C/C++ object has been deleted" error (see e.g. https://github.com/Tribler/tribler/issues/6083) - for data, method in ((patch_data, "PATCH"), (delete_data, "DELETE")): + for data, method in ((patch_data, Request.PATCH), (delete_data, Request.DELETE)): if data: self.remove_items(data) - TriblerNetworkRequest("metadata", lambda _: None, raw_data=json.dumps(data), method=method) + request = Request( + endpoint="metadata", + data=json.dumps(data), + method=method + ) + request_manager.add(request) def create_new_channel(self, channel_name=None): - url = ( - self.endpoint_url_override or "channels/%s/%i" % (self.channel_info["public_key"], self.channel_info["id"]) - ) + ("/channels" if self.channel_info.get("id", 0) == 0 else "/collections") - TriblerNetworkRequest( - url, - self.on_create_query_results, - method='POST', - raw_data=json.dumps({"name": channel_name}) if channel_name else None, + public_key = self.channel_info.get("public_key", '') + channel_id = self.channel_info.get("id", 0) + + endpoint = self.endpoint_url_override or f"channels/{public_key}/{channel_id}" + postfix = "channels" if not channel_id else "collections" + + request = Request( + endpoint=f'{endpoint}/{postfix}', + on_finish=self.on_create_query_results, + method=Request.POST, + data=json.dumps({"name": channel_name}) if channel_name else None, ) + request_manager.add(request) def on_create_query_results(self, response, **kwargs): # This is a hack to put the newly created object at the top of the table diff --git a/src/tribler/gui/widgets/triblertablecontrollers.py b/src/tribler/gui/widgets/triblertablecontrollers.py index 1d29449bd40..73f2f559512 100644 --- a/src/tribler/gui/widgets/triblertablecontrollers.py +++ b/src/tribler/gui/widgets/triblertablecontrollers.py @@ -12,10 +12,10 @@ from tribler.core.components.metadata_store.db.serialization import CHANNEL_TORRENT, COLLECTION_NODE, REGULAR_TORRENT from tribler.core.utilities.simpledefs import CHANNEL_STATE - from tribler.gui.defs import HEALTH_CHECKING, HEALTH_UNCHECKED +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.tribler_action_menu import TriblerActionMenu -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, dict_item_is_any_of, get_health, tr from tribler.gui.widgets.tablecontentmodel import Column @@ -54,9 +54,9 @@ def set_model(self, model): def _on_list_scroll(self, event): # pylint: disable=W0613 if ( - self.table_view.verticalScrollBar().value() == self.table_view.verticalScrollBar().maximum() - and self.model.data_items - and not self.model.all_local_entries_loaded + self.table_view.verticalScrollBar().value() == self.table_view.verticalScrollBar().maximum() + and self.model.data_items + and not self.model.all_local_entries_loaded ): # workaround for duplicate calls to _on_list_scroll on view creation self.model.perform_query() @@ -182,13 +182,17 @@ def check_torrent_health(self, data_item, forced=False): health_cell_index = self.model.index(row, self.model.column_position[Column.HEALTH]) self.model.dataChanged.emit(health_cell_index, health_cell_index, []) - TriblerNetworkRequest( - f"metadata/torrents/{infohash}/health", - self.on_health_response, - url_params={"nowait": True, "refresh": True}, - capture_core_errors=False, + request = Request( + endpoint=f"metadata/torrents/{infohash}/health", + on_finish=self.on_health_response, + url_params={ + "nowait": True, + "refresh": True + }, + capture_errors=False, priority=QNetworkRequest.LowPriority, ) + request_manager.add(request) def on_health_response(self, response): total_seeders = 0 @@ -283,14 +287,15 @@ def _show_context_menu(self, pos): def on_add_to_channel(_): def on_confirm_clicked(channel_id): - TriblerNetworkRequest( - f"collections/mychannel/{channel_id}/copy", - lambda _: self.table_view.window().tray_show_message( + request = Request( + endpoint=f"collections/mychannel/{channel_id}/copy", + on_finish=lambda _: self.table_view.window().tray_show_message( tr("Channel update"), tr("Torrent(s) added to your channel") ), - raw_data=json.dumps(entries), - method='POST', + data=json.dumps(entries), + method=Request.POST, ) + request_manager.add(request) self.table_view.window().add_to_channel_dialog.show_dialog( on_confirm_clicked, confirm_button_text=tr("Copy") @@ -302,7 +307,12 @@ def on_confirm_clicked(channel_id): {'public_key': entry['public_key'], 'id': entry['id'], 'origin_id': channel_id} for entry in entries ] self.model.remove_items(entries) - TriblerNetworkRequest("metadata", lambda _: None, raw_data=json.dumps(changes_list), method='PATCH') + request = Request( + endpoint="metadata", + data=json.dumps(changes_list), + method=Request.PATCH + ) + request_manager.add(request) self.table_view.window().add_to_channel_dialog.show_dialog( on_confirm_clicked, confirm_button_text=tr("Move") diff --git a/src/tribler/gui/widgets/trustgraphpage.py b/src/tribler/gui/widgets/trustgraphpage.py index ecfd587d45a..885e820e9af 100644 --- a/src/tribler/gui/widgets/trustgraphpage.py +++ b/src/tribler/gui/widgets/trustgraphpage.py @@ -1,13 +1,11 @@ import math +import numpy as np +import pyqtgraph as pg from PyQt5 import QtCore from PyQt5.QtNetwork import QNetworkRequest from PyQt5.QtWidgets import QWidget -import numpy as np - -import pyqtgraph as pg - from tribler.gui.defs import ( COLOR_DEFAULT, COLOR_GREEN, @@ -19,8 +17,9 @@ TB, TRUST_GRAPH_PEER_LEGENDS, ) +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect, format_size, html_label, tr @@ -154,17 +153,17 @@ def update_status_bar(self, selected_node): diff = selected_node.get('total_up', 0) - selected_node.get('total_down', 0) color = COLOR_GREEN if diff > 0 else COLOR_RED if diff < 0 else COLOR_DEFAULT bandwidth_message = ( - "Bandwidth " - + HTML_SPACE * 2 - + " Given " - + HTML_SPACE - + html_label(format_size(selected_node.get('total_up', 0))) - + " Taken " - + HTML_SPACE - + html_label(format_size(selected_node.get('total_down', 0))) - + " Balance " - + HTML_SPACE - + html_label(format_size(diff), color=color) + "Bandwidth " + + HTML_SPACE * 2 + + " Given " + + HTML_SPACE + + html_label(format_size(selected_node.get('total_up', 0))) + + " Taken " + + HTML_SPACE + + html_label(format_size(selected_node.get('total_down', 0))) + + " Balance " + + HTML_SPACE + + html_label(format_size(diff), color=color) ) self.window().tr_selected_node_stats.setHidden(False) self.window().tr_selected_node_stats.setText(bandwidth_message) @@ -175,10 +174,14 @@ def reset_graph(self): def fetch_graph_data(self, checked=False): if self.rest_request: - self.rest_request.cancel_request() - self.rest_request = TriblerNetworkRequest( - "trustview?refresh=1", self.on_received_data, priority=QNetworkRequest.LowPriority + self.rest_request.cancel() + self.rest_request = Request( + endpoint="trustview", + url_params={'refresh': 1}, + on_finish=self.on_received_data, + priority=QNetworkRequest.LowPriority ) + request_manager.add(self.rest_request) def on_received_data(self, data): if data is None or not isinstance(data, dict) or 'graph' not in data: diff --git a/src/tribler/gui/widgets/trustpage.py b/src/tribler/gui/widgets/trustpage.py index b65b3dc67bd..3c7b82e8047 100644 --- a/src/tribler/gui/widgets/trustpage.py +++ b/src/tribler/gui/widgets/trustpage.py @@ -4,8 +4,9 @@ from tribler.gui.defs import PB, TB from tribler.gui.dialogs.trustexplanationdialog import TrustExplanationDialog +from tribler.gui.network.request.request import Request +from tribler.gui.network.request_manager import request_manager from tribler.gui.sentry_mixin import AddBreadcrumbOnShowMixin -from tribler.gui.tribler_request_manager import TriblerNetworkRequest from tribler.gui.utilities import connect from tribler.gui.widgets.graphs.dataplot import TimeSeriesDataPlot @@ -67,7 +68,11 @@ def load_history(self) -> None: """ Load the bandwidth balance history by initiating a request to the Tribler core. """ - TriblerNetworkRequest("bandwidth/history", self.received_history) + request = Request( + endpoint="bandwidth/history", + on_finish=self.received_history + ) + request_manager.add(request) def received_history(self, history: Dict): """