Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f5f2843 authored by Joseph Hwang's avatar Joseph Hwang Committed by Gerrit Code Review
Browse files

Merge "floss: btclient: run commands only after adapter is ready" into main

parents fa753e85 5afaf32b
Loading
Loading
Loading
Loading
+45 −39
Original line number Diff line number Diff line
@@ -1312,6 +1312,7 @@ impl RPCProxy for QACallback {

pub(crate) struct MediaCallback {
    objpath: String,
    context: Arc<Mutex<ClientContext>>,

    dbus_connection: Arc<SyncConnection>,
    dbus_crossroads: Arc<Mutex<Crossroads>>,
@@ -1320,10 +1321,11 @@ pub(crate) struct MediaCallback {
impl MediaCallback {
    pub(crate) fn new(
        objpath: String,
        context: Arc<Mutex<ClientContext>>,
        dbus_connection: Arc<SyncConnection>,
        dbus_crossroads: Arc<Mutex<Crossroads>>,
    ) -> Self {
        Self { objpath, dbus_connection, dbus_crossroads }
        Self { objpath, context, dbus_connection, dbus_crossroads }
    }
}

@@ -1351,6 +1353,9 @@ impl IBluetoothMediaCallback for MediaCallback {
        pkt_status_in_hex: String,
        pkt_status_in_binary: String,
    ) {
        // Invoke run_callback so that the callback can be handled through
        // ForegroundActions::RunCallback in main.rs.
        self.context.lock().unwrap().run_callback(Box::new(move |_context| {
            let wbs_dump = if active && wbs {
                let mut to_split_binary = pkt_status_in_binary.clone();
                let mut wrapped_binary = String::new();
@@ -1391,6 +1396,7 @@ impl IBluetoothMediaCallback for MediaCallback {
                if wbs { "mSBC" } else { "CVSD" },
                wbs_dump
            );
        }));
    }
}

+7 −4
Original line number Diff line number Diff line
@@ -366,29 +366,32 @@ impl CommandHandler {
    }

    /// Entry point for command and arguments
    pub fn process_cmd_line(&mut self, command: &str, args: &Vec<String>) {
    pub fn process_cmd_line(&mut self, command: &str, args: &Vec<String>) -> bool {
        // Ignore empty line
        match command {
            "" => {}
            "" => false,
            _ => match self.command_options.get(command) {
                Some(cmd) => {
                    let rules = cmd.rules.clone();
                    match (cmd.function_pointer)(self, &args) {
                        Ok(()) => {}
                        Ok(()) => true,
                        Err(CommandError::InvalidArgs) => {
                            print_error!("Invalid arguments. Usage:\n{}", rules.join("\n"));
                            false
                        }
                        Err(CommandError::Failed(msg)) => {
                            print_error!("Command failed: {}", msg);
                            false
                        }
                    }
                }
                None => {
                    println!("'{}' is an invalid command!", command);
                    self.cmd_help(&args).ok();
                    false
                }
            },
        };
        }
    }

    fn lock_context(&self) -> std::sync::MutexGuard<ClientContext> {
+110 −38
Original line number Diff line number Diff line
@@ -2,12 +2,14 @@ use clap::{value_t, App, Arg};

use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex};
use std::time::Duration;

use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
use dbus::nonblock::SyncConnection;
use dbus_crossroads::Crossroads;
use tokio::sync::mpsc;
use tokio::time::timeout;

use crate::bt_adv::AdvSet;
use crate::bt_gatt::GattClientContext;
@@ -140,6 +142,9 @@ pub(crate) struct ClientContext {

    /// The handle of the SDP record for MPS (Multi-Profile Specification).
    mps_sdp_handle: Option<i32>,

    /// The set of client commands that need to wait for callbacks.
    client_commands_with_callbacks: Vec<String>,
}

impl ClientContext {
@@ -148,6 +153,7 @@ impl ClientContext {
        dbus_crossroads: Arc<Mutex<Crossroads>>,
        tx: mpsc::Sender<ForegroundActions>,
        is_restricted: bool,
        client_commands_with_callbacks: Vec<String>,
    ) -> ClientContext {
        // Manager interface is almost always available but adapter interface
        // requires that the specific adapter is enabled.
@@ -187,6 +193,7 @@ impl ClientContext {
            gatt_client_context: GattClientContext::new(),
            socket_test_schedule: None,
            mps_sdp_handle: None,
            client_commands_with_callbacks,
        }
    }

@@ -305,10 +312,24 @@ enum ForegroundActions {
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let matches = App::new("btclient")
        .arg(Arg::with_name("restricted").long("restricted").takes_value(false))
        .arg(Arg::with_name("command").short("c").long("command").takes_value(true))
        .arg(
            Arg::with_name("command")
                .short("c")
                .long("command")
                .takes_value(true)
                .help("Executes a non-interactive command"),
        )
        .arg(
            Arg::with_name("timeout")
                .short("t")
                .long("timeout")
                .takes_value(true)
                .help("Specify a timeout in seconds for a non-interactive command"),
        )
        .get_matches();
    let command = value_t!(matches, "command", String);
    let is_restricted = matches.is_present("restricted");
    let timeout_secs = value_t!(matches, "timeout", u64);

    topstack::get_runtime().block_on(async move {
        // Connect to D-Bus system bus.
@@ -341,12 +362,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
        // Accept foreground actions with mpsc
        let (tx, rx) = mpsc::channel::<ForegroundActions>(10);

        // Include the commands
        // (1) that will be run as non-interactive client commands, and
        // (2) that will need to wait for the callbacks to complete.
        let client_commands_with_callbacks = vec!["media".to_string()];

        // Create the context needed for handling commands
        let context = Arc::new(Mutex::new(ClientContext::new(
            conn.clone(),
            cr.clone(),
            tx.clone(),
            is_restricted,
            client_commands_with_callbacks,
        )));

        // Check if manager interface is valid. We only print some help text before failing on the
@@ -385,36 +412,57 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
            }
        }

        let mut handler = CommandHandler::new(context.clone());

        // Allow command line arguments to be read
        match command {
            Ok(command) => {
                let mut iter = command.split(' ').map(String::from);
                handler.process_cmd_line(
                    &iter.next().unwrap_or(String::from("")),
                    &iter.collect::<Vec<String>>(),
                );
        let handler = CommandHandler::new(context.clone());
        if let Ok(_) = command {
            // Timeout applies only to non-interactive commands.
            if let Ok(timeout_secs) = timeout_secs {
                let timeout_duration = Duration::from_secs(timeout_secs);
                match timeout(
                    timeout_duration,
                    handle_client_command(handler, tx, rx, context, command),
                )
                .await
                {
                    Ok(_) => {
                        return Result::Ok(());
                    }
            _ => {
                start_interactive_shell(handler, tx, rx, context).await?;
                    Err(_) => {
                        return Result::Err("btclient timeout".into());
                    }
                };
            }
        }
        // There are two scenarios in which handle_client_command is run without a timeout.
        // - Interactive commands: none of these commands require a timeout.
        // - Non-interactive commands that have not specified a timeout.
        handle_client_command(handler, tx, rx, context, command).await?;
        return Result::Ok(());
    })
}

async fn start_interactive_shell(
// If btclient runs without command arguments, the interactive shell
// actions are performed.
// If btclient runs with command arguments, the command is executed
// once. There are two cases to exit.
//   Case 1: if the command does not need a callback, e.g., "help",
//           it will exit after running handler.process_cmd_line().
//   Case 2: if the command needs a callback, e.g., "media log",
//           it will exit after the callback has been run in the arm
//           of ForegroundActions::RunCallback(callback).
async fn handle_client_command(
    mut handler: CommandHandler,
    tx: mpsc::Sender<ForegroundActions>,
    mut rx: mpsc::Receiver<ForegroundActions>,
    context: Arc<Mutex<ClientContext>>,
    command: Result<String, clap::Error>,
) -> Result<(), Box<dyn std::error::Error>> {
    let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1));

    // If there are no command arguments, start the interactive shell.
    if let Err(_) = command {
        let command_rule_list = handler.get_command_rule_list().clone();
        let context_for_closure = context.clone();

    let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1));

        // Async task to keep reading new lines from user
        let semaphore = semaphore_fg.clone();
        let editor = AsyncEditor::new(command_rule_list, context_for_closure)
@@ -434,6 +482,7 @@ async fn start_interactive_shell(
                let _ = tx.send(ForegroundActions::Readline(result)).await;
            }
        });
    }

    'readline: loop {
        let m = rx.recv().await;
@@ -458,6 +507,11 @@ async fn start_interactive_shell(
            }
            ForegroundActions::RunCallback(callback) => {
                callback(context.clone());

                // Break the loop as a non-interactive command is completed.
                if let Ok(_) = command {
                    break;
                }
            }
            // Once adapter is ready, register callbacks, get the address and mark it as ready
            ForegroundActions::RegisterAdapterCallback(adapter) => {
@@ -619,6 +673,7 @@ async fn start_interactive_shell(
                    .rpc
                    .register_callback(Box::new(MediaCallback::new(
                        media_cb_objpath,
                        context.clone(),
                        dbus_connection.clone(),
                        dbus_crossroads.clone(),
                    )))
@@ -630,6 +685,23 @@ async fn start_interactive_shell(
                context.lock().unwrap().update_bonded_devices();

                print_info!("Adapter {} is ready", adapter_address);

                // Run the command with the command arguments as the client is
                // non-interactive.
                if let Some(command) = command.as_ref().ok() {
                    let mut iter = command.split(' ').map(String::from);
                    let first = iter.next().unwrap_or(String::from(""));
                    if !handler.process_cmd_line(&first, &iter.collect::<Vec<String>>()) {
                        // Break immediately if the command fails to execute.
                        break;
                    }

                    // Break the loop immediately if there is no callback
                    // to wait for.
                    if !context.lock().unwrap().client_commands_with_callbacks.contains(&first) {
                        break;
                    }
                }
            }
            ForegroundActions::Readline(result) => match result {
                Err(rustyline::error::ReadlineError::Interrupted) => {