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
|
// Best-effort: make ourselves reachable behind a home router so
|
||||||
// firewalled peers can connect back. Mapped only once the listener is
|
// firewalled peers can connect back. Mapped only once the listener is
|
||||||
// up, and for the port it really bound. Kept alive for the session.
|
// up, and for the port it really bound. Kept alive for the session.
|
||||||
let _port_mapper = outcome
|
let bound_port = outcome.client.listen_port();
|
||||||
.client
|
let _port_mapper =
|
||||||
.listen_port()
|
bound_port.map(crate::port_mapping::PortMapper::spawn);
|
||||||
.map(crate::port_mapping::PortMapper::spawn);
|
|
||||||
|
|
||||||
let store = crate::persist::paths::state_dir()
|
let store = crate::persist::paths::state_dir()
|
||||||
.map(crate::persist::state::StateStore::new);
|
.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(
|
let exit = launch_main_tui(
|
||||||
terminal,
|
terminal,
|
||||||
Arc::new(outcome.client),
|
Arc::new(outcome.client),
|
||||||
@@ -517,6 +520,7 @@ fn run_default_tui(
|
|||||||
Duration::from_secs(resolved.search_timeout),
|
Duration::from_secs(resolved.search_timeout),
|
||||||
store,
|
store,
|
||||||
config_path.clone(),
|
config_path.clone(),
|
||||||
|
listener_fallback,
|
||||||
)
|
)
|
||||||
.map_err(|e| CliError::new(Exit::Failure, e.to_string()))?;
|
.map_err(|e| CliError::new(Exit::Failure, e.to_string()))?;
|
||||||
|
|
||||||
@@ -592,6 +596,7 @@ fn run_attached_tui(
|
|||||||
Duration::from_secs(resolved.search_timeout),
|
Duration::from_secs(resolved.search_timeout),
|
||||||
None,
|
None,
|
||||||
config_path,
|
config_path,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|e| CliError::new(Exit::Failure, e.to_string()))
|
.map_err(|e| CliError::new(Exit::Failure, e.to_string()))
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use ratatui::{
|
|||||||
widgets::Paragraph,
|
widgets::Paragraph,
|
||||||
};
|
};
|
||||||
use soulseek_rs::DownloadStatus;
|
use soulseek_rs::DownloadStatus;
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ pub fn render_download_stats(
|
|||||||
downloads: &[DownloadEntry],
|
downloads: &[DownloadEntry],
|
||||||
active_count: usize,
|
active_count: usize,
|
||||||
daemon: Option<&str>,
|
daemon: Option<&str>,
|
||||||
|
listener_fallback: Option<(u16, u16)>,
|
||||||
) {
|
) {
|
||||||
let completed = downloads
|
let completed = downloads
|
||||||
.iter()
|
.iter()
|
||||||
@@ -79,10 +81,14 @@ pub fn render_download_stats(
|
|||||||
|
|
||||||
let active = active_progress(downloads);
|
let active = active_progress(downloads);
|
||||||
|
|
||||||
let title = match daemon {
|
let mut title = match daemon {
|
||||||
Some(endpoint) => format!("Status · daemon {endpoint}"),
|
Some(endpoint) => format!("Status · daemon {endpoint}"),
|
||||||
None => "Status".to_string(),
|
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 block = pane_block(false).title(plain_title(&title, false));
|
||||||
|
|
||||||
let inner_area = block.inner(area);
|
let inner_area = block.inner(area);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub struct MainTui {
|
|||||||
saved_snapshot: Snapshot,
|
saved_snapshot: Snapshot,
|
||||||
/// Which divider the left mouse button is holding, if any.
|
/// Which divider the left mouse button is holding, if any.
|
||||||
resize_drag: Option<input::ResizeDrag>,
|
resize_drag: Option<input::ResizeDrag>,
|
||||||
|
listener_fallback: Option<(u16, u16)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MainTui {
|
impl MainTui {
|
||||||
@@ -53,6 +54,7 @@ impl MainTui {
|
|||||||
config_path,
|
config_path,
|
||||||
saved_snapshot: Snapshot::default(),
|
saved_snapshot: Snapshot::default(),
|
||||||
resize_drag: None,
|
resize_drag: None,
|
||||||
|
listener_fallback: None,
|
||||||
};
|
};
|
||||||
tui.restore_persisted_state();
|
tui.restore_persisted_state();
|
||||||
tui
|
tui
|
||||||
@@ -257,9 +259,11 @@ pub fn launch_main_tui(
|
|||||||
search_timeout: Duration,
|
search_timeout: Duration,
|
||||||
store: Option<StateStore>,
|
store: Option<StateStore>,
|
||||||
config_path: Option<std::path::PathBuf>,
|
config_path: Option<std::path::PathBuf>,
|
||||||
|
listener_fallback: Option<(u16, u16)>,
|
||||||
) -> Result<TuiExit> {
|
) -> Result<TuiExit> {
|
||||||
let tui =
|
let mut tui =
|
||||||
MainTui::new(client, download_dir, search_timeout, store, config_path);
|
MainTui::new(client, download_dir, search_timeout, store, config_path);
|
||||||
|
tui.listener_fallback = listener_fallback;
|
||||||
tui.run(terminal)
|
tui.run(terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ impl MainTui {
|
|||||||
&self.state.downloads,
|
&self.state.downloads,
|
||||||
self.state.active_downloads_count,
|
self.state.active_downloads_count,
|
||||||
daemon.as_deref(),
|
daemon.as_deref(),
|
||||||
|
self.listener_fallback,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.render_content(frame, main_chunks[1]);
|
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}");
|
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]
|
#[test]
|
||||||
fn tab_and_shift_tab_walk_the_panes_in_legend_order() {
|
fn tab_and_shift_tab_walk_the_panes_in_legend_order() {
|
||||||
let mut tui = furnished_tui();
|
let mut tui = furnished_tui();
|
||||||
|
|||||||
Reference in New Issue
Block a user