From 31ab84869465e2b292a4a1c80223165d892cc986 Mon Sep 17 00:00:00 2001 From: Sebastian Holmin Date: Fri, 23 Aug 2024 09:21:32 +0200 Subject: [PATCH] WIP: only show diffs in new tunnel states --- mullvad-cli/src/cmds/status.rs | 62 +++----- mullvad-cli/src/cmds/tunnel_state.rs | 2 +- mullvad-cli/src/format.rs | 209 +++++++++++++++++++++------ 3 files changed, 185 insertions(+), 88 deletions(-) diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index d7e646f42369..e284592910c7 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -30,9 +30,11 @@ pub struct StatusArgs { } impl Status { - pub async fn listen(mut rpc: MullvadProxyClient, args: StatusArgs) -> Result<()> { - let mut previous_tunnel_state = None; - + pub async fn listen( + mut rpc: MullvadProxyClient, + args: StatusArgs, + mut previous_tunnel_state: TunnelState, + ) -> Result<()> { let mut event_stream = rpc.events_listen().await?; while let Some(event) = event_stream.next().await { match event? { @@ -44,39 +46,8 @@ impl Status { .context("Failed to format output as JSON")?; println!("{json}"); } else { - // When we enter the connected or disconnected state, am.i.mullvad.net will - // be polled to get exit location. When it arrives, we will get another - // tunnel state of the same enum type, but with the location filled in. This - // match statement checks if the new state is an updated version of the old - // one and if so skips the print to avoid spamming the user. Note that for - // graphical frontends updating the drawn state with an identical one is - // invisible, so this is only an issue for the CLI. - match (&previous_tunnel_state, &new_state) { - ( - Some(TunnelState::Disconnected { - location: _, - locked_down: was_locked_down, - }), - TunnelState::Disconnected { - location: _, - locked_down, - }, - // Do print an updated state if the lockdown setting was changed - ) if was_locked_down == locked_down => continue, - ( - Some(TunnelState::Connected { - feature_indicators: old_feature_indicators, - .. - }), - TunnelState::Connected { - feature_indicators, .. - }, - // Do print an updated state if the feature indicators changed - ) if old_feature_indicators == feature_indicators => continue, - _ => {} - } - format::print_state(&new_state, args.verbose); - previous_tunnel_state = Some(new_state); + format::print_state(&new_state, Some(&previous_tunnel_state), args.verbose); + previous_tunnel_state = new_state; } } DaemonEvent::Settings(settings) => { @@ -116,12 +87,25 @@ pub async fn handle(cmd: Option, args: StatusArgs) -> Result<()> { let json = serde_json::to_string(&state).context("Failed to format output as JSON")?; println!("{json}"); } else { - format::print_state(&state, args.verbose); - format::print_location(&state); + format::print_state(&state, None, args.verbose); + match &state { + TunnelState::Disconnected { + location: Some(location), + locked_down: _, + } + | TunnelState::Connected { + location: Some(location), + .. + } => { + print!(" "); + format::print_location(location); + } + _ => {} + } } if cmd == Some(Status::Listen) { - Status::listen(rpc, args).await?; + Status::listen(rpc, args, state).await?; } Ok(()) } diff --git a/mullvad-cli/src/cmds/tunnel_state.rs b/mullvad-cli/src/cmds/tunnel_state.rs index 76393091c580..b05316b76501 100644 --- a/mullvad-cli/src/cmds/tunnel_state.rs +++ b/mullvad-cli/src/cmds/tunnel_state.rs @@ -81,7 +81,7 @@ async fn wait_for_tunnel_state( ) -> Result<()> { while let Some(state) = event_stream.next().await { if let DaemonEvent::TunnelState(new_state) = state? { - format::print_state(&new_state, false); + format::print_state(&new_state, None, false); if matches_event(&new_state)? { return Ok(()); } diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs index 63395ea87afc..4df82b654181 100644 --- a/mullvad-cli/src/format.rs +++ b/mullvad-cli/src/format.rs @@ -18,77 +18,190 @@ macro_rules! print_option { }}; } -pub fn print_state(state: &TunnelState, verbose: bool) { +pub fn print_state(state: &TunnelState, previous_state: Option<&TunnelState>, verbose: bool) { use TunnelState::*; - match state { - Error(error) => print_error_state(error), - Connected { - endpoint, - location, - feature_indicators, - } => { + // When we enter the connected or disconnected state, am.i.mullvad.net will + // be polled to get exit location. When it arrives, we will get another + // tunnel state of the same enum type, but with the location filled in. This + // match statement checks if the new state is an updated version of the old + // one and if so skips the print to avoid spamming the user. Note that for + // graphical frontends updating the drawn state with an identical one is + // invisible, so this is only an issue for the CLI. + match (&previous_state, &state) { + (_, TunnelState::Error(e)) => print_error_state(e), + ( + Some(TunnelState::Disconnected { + location: old_location, + locked_down: was_locked_down, + }), + TunnelState::Disconnected { + location, + locked_down, + }, + // Do print an updated state if the lockdown setting was changed + ) => { + if *locked_down && !was_locked_down { + println!(" Internet access is blocked due to lockdown mode"); + } else if !*locked_down && *was_locked_down { + println!(" Internet access is no longer blocked due to lockdown mode"); + } + + if location != old_location { + if let Some(location) = location { + print!(" "); + print_location(location); // TODO: look over + } + } + } + ( + _, + TunnelState::Disconnected { + location, + locked_down, + }, + // Do print an updated state if the lockdown setting was changed + ) => { + println!("Disconnected"); + if *locked_down { + println!("Internet access is blocked due to lockdown mode"); + } + if let Some(location) = location { + print!(" "); + print_location(location); + } + } + ( + Some(TunnelState::Connected { + feature_indicators: old_feature_indicators, + location: old_location, + .. + }), + TunnelState::Connected { + feature_indicators, + location, + .. + }, + ) => { + if feature_indicators != old_feature_indicators { + print!(" "); + println!( + "New features: {}", + format_feature_indicators(feature_indicators) + ); + } + + if location != old_location { + if let Some(location) = location { + print!(" "); + print_location(location); + } + } + } + ( + _, + TunnelState::Connected { + endpoint, + location, + feature_indicators, + }, + // Do print an updated state if the feature indicators changed + ) => { println!( "Connected to {}", format_relay_connection(endpoint, location.as_ref(), verbose) ); + let features = format_feature_indicators(feature_indicators); + if !features.is_empty() { + println!("Active features: {}", features); + } + + // if let Some(location) = location { + // print_location(location); + // } + if verbose { + if let Some(tunnel_interface) = &endpoint.tunnel_interface { + print!(" "); + println!("Tunnel interface: {tunnel_interface}") + } + } + } + ( + Some(TunnelState::Connecting { + feature_indicators: old_feature_indicators, + endpoint: old_endpoint, + location: old_location, + }), + TunnelState::Connecting { + feature_indicators, + endpoint, + location, + }, + // Do print an updated state if the feature indicators changed + ) => { + if endpoint != old_endpoint || location != old_location { + println!( + "New connection attempt to {}...", + format_relay_connection(state.endpoint().unwrap(), location.as_ref(), verbose) + ); + } + + if feature_indicators != old_feature_indicators { + print!(" "); println!( - "Active features: {}", + "New features: {}", format_feature_indicators(feature_indicators) ); - if let Some(tunnel_interface) = &endpoint.tunnel_interface { + } + if verbose { + print!(" "); + if let Some(tunnel_interface) = &state.endpoint().unwrap().tunnel_interface { println!("Tunnel interface: {tunnel_interface}") } } } - Connecting { - endpoint, - location, - feature_indicators: _, - } => { - let ellipsis = if !verbose { "..." } else { "" }; + ( + _, + Connecting { + endpoint, + location, + feature_indicators, + }, + // Do print an updated state if the feature indicators changed + ) => { println!( - "Connecting to {}{ellipsis}", + "Connecting to {}...", format_relay_connection(endpoint, location.as_ref(), verbose) ); - } - Disconnected { - location: _, - locked_down, - } => { - if *locked_down { - println!("Disconnected (Internet access is blocked due to lockdown mode)"); - } else { - println!("Disconnected"); + let features = format_feature_indicators(feature_indicators); + if !features.is_empty() { + print!(" "); + println!("Features: {}", features); + } + if verbose { + if let Some(tunnel_interface) = &endpoint.tunnel_interface { + print!(" "); + println!("Tunnel interface: {tunnel_interface}") + } } } - Disconnecting(_) => println!("Disconnecting..."), + (_, Disconnecting(_)) => println!("Disconnecting..."), } } -pub fn print_location(state: &TunnelState) { - let location = match state { - TunnelState::Disconnected { - location, - locked_down: _, - } => location, - TunnelState::Connected { location, .. } => location, - _ => return, - }; - if let Some(location) = location { - print!("Your connection appears to be from: {}", location.country); - if let Some(city) = &location.city { - print!(", {}", city); - } - if let Some(ipv4) = location.ipv4 { - print!(". IPv4: {ipv4}"); - } - if let Some(ipv6) = location.ipv6 { - print!(", IPv6: {ipv6}"); - } - println!(); +pub fn print_location(location: &GeoIpLocation) { + print!("Your connection appears to be from: {}", location.country); + if let Some(city) = &location.city { + print!(", {}", city); + } + if let Some(ipv4) = location.ipv4 { + print!(". IPv4: {ipv4}"); + } + if let Some(ipv6) = location.ipv6 { + print!(", IPv6: {ipv6}"); } + println!(); } fn format_relay_connection(