diff --git a/electrum/gui/icons/bookmark.png b/electrum/gui/icons/bookmark.png new file mode 100644 index 000000000..dd24a0c15 Binary files /dev/null and b/electrum/gui/icons/bookmark.png differ diff --git a/electrum/gui/icons/bookmark.svg b/electrum/gui/icons/bookmark.svg new file mode 100644 index 000000000..32123634d --- /dev/null +++ b/electrum/gui/icons/bookmark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/electrum/gui/icons/bookmark_add.png b/electrum/gui/icons/bookmark_add.png new file mode 100644 index 000000000..430861c47 Binary files /dev/null and b/electrum/gui/icons/bookmark_add.png differ diff --git a/electrum/gui/icons/bookmark_add.svg b/electrum/gui/icons/bookmark_add.svg new file mode 100644 index 000000000..ed4b981d4 --- /dev/null +++ b/electrum/gui/icons/bookmark_add.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/electrum/gui/icons/bookmark_remove.png b/electrum/gui/icons/bookmark_remove.png new file mode 100644 index 000000000..a75c86c6b Binary files /dev/null and b/electrum/gui/icons/bookmark_remove.png differ diff --git a/electrum/gui/icons/bookmark_remove.svg b/electrum/gui/icons/bookmark_remove.svg new file mode 100644 index 000000000..a6036a19f --- /dev/null +++ b/electrum/gui/icons/bookmark_remove.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/electrum/gui/qt/network_dialog.py b/electrum/gui/qt/network_dialog.py index 141987fc8..1ad70a090 100644 --- a/electrum/gui/qt/network_dialog.py +++ b/electrum/gui/qt/network_dialog.py @@ -123,6 +123,13 @@ class NodesListWidget(QTreeWidget): def do_set_server(): self.setServer.emit(str(server)) menu.addAction(read_QIcon("chevron-right.png"), _("Use as server"), do_set_server) + def set_bookmark(*, add: bool): + self.network.set_server_bookmark(server, add=add) + self.update() + if self.network.is_server_bookmarked(server): + menu.addAction(read_QIcon("bookmark_remove.png"), _("Remove from bookmarks"), lambda: set_bookmark(add=False)) + else: + menu.addAction(read_QIcon("bookmark_add.png"), _("Bookmark this server"), lambda: set_bookmark(add=True)) elif item_type == self.ItemType.CHAIN: chain_id = item.data(0, self.CHAIN_ID_ROLE) def do_follow_chain(): @@ -174,6 +181,8 @@ class NodesListWidget(QTreeWidget): item.setToolTip(0, str(i.server)) if i == network.interface: item.setIcon(0, read_QIcon("chevron-right.png")) + elif network.is_server_bookmarked(i.server): + item.setIcon(0, read_QIcon("bookmark.png")) x.addChild(item) if n_chains > 1: connected_servers_item.addChild(x) @@ -183,18 +192,21 @@ class NodesListWidget(QTreeWidget): disconnected_servers_item.setData(0, self.ITEMTYPE_ROLE, self.ItemType.TOPLEVEL) connected_hosts = set([iface.host for ifaces in chains.values() for iface in ifaces]) protocol = PREFERRED_NETWORK_PROTOCOL - for _host, d in sorted(servers.items()): - if _host in connected_hosts: - continue - if _host.endswith('.onion') and not use_tor: + server_addrs = [ + ServerAddr(_host, port, protocol=protocol) + for _host, d in servers.items() + if (port := d.get(protocol))] + server_addrs.sort(key=lambda x: (-network.is_server_bookmarked(x), str(x))) + for server in server_addrs: + if server.host in connected_hosts: continue - port = d.get(protocol) - if not port: + if server.host.endswith('.onion') and not use_tor: continue - server = ServerAddr(_host, port, protocol=protocol) item = QTreeWidgetItem([server.net_addr_str(), ""]) item.setData(0, self.ITEMTYPE_ROLE, self.ItemType.DISCONNECTED_SERVER) item.setData(0, self.SERVER_ADDR_ROLE, server) + if network.is_server_bookmarked(server): + item.setIcon(0, read_QIcon("bookmark.png")) disconnected_servers_item.addChild(item) self.addTopLevelItem(connected_servers_item) diff --git a/electrum/network.py b/electrum/network.py index d3c2b1b7e..ddef1aa8f 100644 --- a/electrum/network.py +++ b/electrum/network.py @@ -581,6 +581,18 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): out[server.host].update({server.protocol: port}) else: out[server.host] = {server.protocol: port} + # add bookmarks + bookmarks = self.config.NETWORK_BOOKMARKED_SERVERS or [] + for server_str in bookmarks: + try: + server = ServerAddr.from_str(server_str) + except ValueError: + continue + port = str(server.port) + if server.host in out: + out[server.host].update({server.protocol: port}) + else: + out[server.host] = {server.protocol: port} # potentially filter out some if self.config.NETWORK_NOONION: out = filter_noonion(out) @@ -706,6 +718,22 @@ class Network(Logger, NetworkRetryManager[ServerAddr]): self.oneserver = oneserver self.num_server = NUM_TARGET_CONNECTED_SERVERS if not oneserver else 0 + def is_server_bookmarked(self, server: ServerAddr) -> bool: + bookmarks = self.config.NETWORK_BOOKMARKED_SERVERS or [] + return str(server) in bookmarks + + def set_server_bookmark(self, server: ServerAddr, *, add: bool) -> None: + server_str = str(server) + with self.config.lock: + bookmarks = self.config.NETWORK_BOOKMARKED_SERVERS or [] + if add: + if server_str not in bookmarks: + bookmarks.append(server_str) + else: # remove + if server_str in bookmarks: + bookmarks.remove(server_str) + self.config.NETWORK_BOOKMARKED_SERVERS = bookmarks + async def _switch_to_random_interface(self): '''Switch to a random connected server other than the current one''' servers = self.get_interfaces() # Those in connected state diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 02d6a9db1..e3b410e6a 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -940,6 +940,7 @@ class SimpleConfig(Logger): NETWORK_SERVERFINGERPRINT = ConfigVar('serverfingerprint', default=None, type_=str) NETWORK_MAX_INCOMING_MSG_SIZE = ConfigVar('network_max_incoming_msg_size', default=1_000_000, type_=int) # in bytes NETWORK_TIMEOUT = ConfigVar('network_timeout', default=None, type_=int) + NETWORK_BOOKMARKED_SERVERS = ConfigVar('network_bookmarked_servers', default=None) WALLET_BATCH_RBF = ConfigVar( 'batch_rbf', default=False, type_=bool,