feat(tui): say so in the status bar when the listener took another port
With the login screen's own attempts no longer holding the port, a session listens somewhere other than the configured port mainly when another program already holds it. soulseek-rs still falls back to an ephemeral port there, which is what lets several clients on one machine share a configured port, and it advertises the port it really bound. A manual port forward on the router then points at nothing, though, and the TUI never said so; the CLI already warns. After login the TUI compares the bound port with the configured one, using the CLI's rule, and the status bar's title reads "port X in use, listening on Y" for the rest of the session. A window attached to a daemon shows nothing: the daemon owns its listener. Nicotine+ keeps retrying the configured port every five seconds and Soulseek.NET refuses it; neither falls back. Keeping the fallback and making it visible is a deliberate difference.
This commit is contained in:
@@ -502,14 +502,17 @@ fn run_default_tui(
|
||||
// Best-effort: make ourselves reachable behind a home router so
|
||||
// firewalled peers can connect back. Mapped only once the listener is
|
||||
// up, and for the port it really bound. Kept alive for the session.
|
||||
let _port_mapper = outcome
|
||||
.client
|
||||
.listen_port()
|
||||
.map(crate::port_mapping::PortMapper::spawn);
|
||||
let bound_port = outcome.client.listen_port();
|
||||
let _port_mapper =
|
||||
bound_port.map(crate::port_mapping::PortMapper::spawn);
|
||||
|
||||
let store = crate::persist::paths::state_dir()
|
||||
.map(crate::persist::state::StateStore::new);
|
||||
|
||||
let listener_fallback = bound_port
|
||||
.filter(|&bound| listen_port != 0 && bound != listen_port)
|
||||
.map(|bound| (listen_port, bound));
|
||||
|
||||
let exit = launch_main_tui(
|
||||
terminal,
|
||||
Arc::new(outcome.client),
|
||||
@@ -517,6 +520,7 @@ fn run_default_tui(
|
||||
Duration::from_secs(resolved.search_timeout),
|
||||
store,
|
||||
config_path.clone(),
|
||||
listener_fallback,
|
||||
)
|
||||
.map_err(|e| CliError::new(Exit::Failure, e.to_string()))?;
|
||||
|
||||
@@ -592,6 +596,7 @@ fn run_attached_tui(
|
||||
Duration::from_secs(resolved.search_timeout),
|
||||
None,
|
||||
config_path,
|
||||
None,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| CliError::new(Exit::Failure, e.to_string()))
|
||||
|
||||
@@ -12,6 +12,7 @@ use ratatui::{
|
||||
widgets::Paragraph,
|
||||
};
|
||||
use soulseek_rs::DownloadStatus;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -54,6 +55,7 @@ pub fn render_download_stats(
|
||||
downloads: &[DownloadEntry],
|
||||
active_count: usize,
|
||||
daemon: Option<&str>,
|
||||
listener_fallback: Option<(u16, u16)>,
|
||||
) {
|
||||
let completed = downloads
|
||||
.iter()
|
||||
@@ -79,10 +81,14 @@ pub fn render_download_stats(
|
||||
|
||||
let active = active_progress(downloads);
|
||||
|
||||
let title = match daemon {
|
||||
let mut title = match daemon {
|
||||
Some(endpoint) => format!("Status · daemon {endpoint}"),
|
||||
None => "Status".to_string(),
|
||||
};
|
||||
if let Some((configured, bound)) = listener_fallback {
|
||||
let _ =
|
||||
write!(title, " · port {configured} in use, listening on {bound}");
|
||||
}
|
||||
let block = pane_block(false).title(plain_title(&title, false));
|
||||
|
||||
let inner_area = block.inner(area);
|
||||
|
||||
@@ -33,6 +33,7 @@ pub struct MainTui {
|
||||
saved_snapshot: Snapshot,
|
||||
/// Which divider the left mouse button is holding, if any.
|
||||
resize_drag: Option<input::ResizeDrag>,
|
||||
listener_fallback: Option<(u16, u16)>,
|
||||
}
|
||||
|
||||
impl MainTui {
|
||||
@@ -53,6 +54,7 @@ impl MainTui {
|
||||
config_path,
|
||||
saved_snapshot: Snapshot::default(),
|
||||
resize_drag: None,
|
||||
listener_fallback: None,
|
||||
};
|
||||
tui.restore_persisted_state();
|
||||
tui
|
||||
@@ -257,9 +259,11 @@ pub fn launch_main_tui(
|
||||
search_timeout: Duration,
|
||||
store: Option<StateStore>,
|
||||
config_path: Option<std::path::PathBuf>,
|
||||
listener_fallback: Option<(u16, u16)>,
|
||||
) -> Result<TuiExit> {
|
||||
let tui =
|
||||
let mut tui =
|
||||
MainTui::new(client, download_dir, search_timeout, store, config_path);
|
||||
tui.listener_fallback = listener_fallback;
|
||||
tui.run(terminal)
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ impl MainTui {
|
||||
&self.state.downloads,
|
||||
self.state.active_downloads_count,
|
||||
daemon.as_deref(),
|
||||
self.listener_fallback,
|
||||
);
|
||||
|
||||
self.render_content(frame, main_chunks[1]);
|
||||
|
||||
@@ -21,6 +21,19 @@ fn the_results_pane_has_the_whole_width_to_itself() {
|
||||
assert!(searches < downloads && downloads < info, "{row}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_listener_that_fell_back_says_so_in_the_status_bar() {
|
||||
let mut tui = furnished_tui();
|
||||
tui.listener_fallback = Some((2234, 50123));
|
||||
|
||||
let screen = screen_of(&mut tui);
|
||||
|
||||
assert!(
|
||||
screen.contains("port 2234 in use, listening on 50123"),
|
||||
"{screen}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tab_and_shift_tab_walk_the_panes_in_legend_order() {
|
||||
let mut tui = furnished_tui();
|
||||
|
||||
Reference in New Issue
Block a user