Skip to content

Commit

Permalink
WIP: only show diffs in new tunnel states
Browse files Browse the repository at this point in the history
  • Loading branch information
Serock3 committed Aug 26, 2024
1 parent b235a4d commit 31ab848
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 88 deletions.
62 changes: 23 additions & 39 deletions mullvad-cli/src/cmds/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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? {
Expand All @@ -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) => {
Expand Down Expand Up @@ -116,12 +87,25 @@ pub async fn handle(cmd: Option<Status>, 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(())
}
Expand Down
2 changes: 1 addition & 1 deletion mullvad-cli/src/cmds/tunnel_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(());
}
Expand Down
209 changes: 161 additions & 48 deletions mullvad-cli/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 31ab848

Please sign in to comment.