rustsveltetauri

How do I get a live stream of data from a command line program in Rust?


I'm building a tauri desktop app around tshark and want to call out to the cli in Rust and pipe results back to the UI (svelte).

THis is my main.rs in tauri:

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use std::process::Stdio;
use tauri::Manager;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command; // Ensure this is actually used elsewhere, or remove it.

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

// Define the function that starts tshark and captures its output
#[tauri::command]
// Use async function and pass the Tauri AppHandle
async fn start_tshark(app: tauri::AppHandle) -> Result<(), String> {
    let mut child = Command::new("sudo")
        .args(["tshark", "-i", "any"])
        .stdout(Stdio::piped())
        .spawn()
        .map_err(|e| e.to_string())?;

    let stdout = child.stdout.take().ok_or("Failed to capture stdout")?;
    let reader = BufReader::new(stdout);
    let mut lines = reader.lines();

    let main_window = app.get_window("main").ok_or("Main window not found")?;

    while let Ok(Some(line)) = lines.next_line().await {
        if let Err(e) = main_window.emit("tshark-output", &line) {
            return Err(e.to_string());
        }
    }

    // Correctly await the completion of the child process
    if let Err(e) = child.wait().await {
        return Err(format!("Tshark process failed: {}", e));
    }

    Ok(())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![start_tshark, greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

and this is my client side listener in svelte:

<script>
  import { onMount, onDestroy } from "svelte";
  import { invoke } from "@tauri-apps/api/tauri";
  import { listen } from "@tauri-apps/api/event";
  import Greet from "../components/Greet.svelte";

  let unlisten; // To hold the function to unlisten to events

  onMount(() => {
    const startBtn = document.getElementById("start-tshark");
    const output = document.getElementById("output");

    startBtn.onclick = async () => {
      try {
        const res = await invoke("start_tshark");

        // Set up the event listener
        unlisten = await listen("tshark-output", (event) => {
          console.log("Received from Rust:", event.payload);
          output.textContent = event.payload;
        });
      } catch (err) {
        output.textContent = err;
      }
    };
  });

  // Ensure to clean up the event listener when the component is destroyed
  onDestroy(() => {
    if (unlisten) {
      unlisten();
    }
  });
</script>

<button id="start-tshark">Start Tshark</button>
<button id="stop-tshark">Stop Tshark</button>
<pre id="output"></pre>

<Greet />

I don't get anymore compile errors, but the UI just hangs, I see the backend is calling tshark and logging output but nothing gets sent to the frontend.


Solution

  • This:

    const res = await invoke("start_tshark");
    

    means your code waits for the invocation of start_tshark to finish, and only then continues to registering the listener. But the invocation never finishes because it waits for all tshark messages.

    Since you don't need to wait for the invocation to finish, just remove the const res = await.