実行に時間がかかるプロセスを管理する

Recipe ID: shell-007

サーバープロセスや長時間の計算処理など、すぐに終了しないコマンドを管理する方法を解説します。
execute() ではなく spawn() を使用し、プロセスID(PID)の取得や状態管理を行います。

前提条件

プラグインのインストールが必要です。

npm run tauri add shell

Permissions (権限) の設定

src-tauri/capabilities/default.json に以下の権限を追加します。

{
  "permissions": [
    ...,
    "shell:allow-open",
    {
      "identifier": "shell:allow-execute",
      "allow": [
        {
          "name": "python",
          "cmd": "python",
          "args": true
        }
      ]
    },
    {
      "identifier": "shell:allow-spawn",
      "allow": [
        {
          "name": "python",
          "cmd": "python",
          "args": true
        }
      ]
    }
  ]
}

1. フロントエンドから作成する (TypeScript)

cmd.spawn()Promise<Child> を返します。
Child オブジェクトを通じて、PID の取得や、プロセス終了時のハンドリングが可能になります。

import { Command, Child } from '@tauri-apps/plugin-shell';

let runningProcess: Child | null = null;

async function startServer() {
  const cmd = Command.create('python', ['-m', 'http.server', '8080']);
  
  // イベントリスナーの設定
  cmd.on('close', () => {
    console.log('Server stopped');
    runningProcess = null;
  });

  cmd.on('error', err => console.error('Server error:', err));
  
  cmd.stdout.on('data', line => console.log(`[SERVER]: ${line}`));

  // プロセスの開始
  runningProcess = await cmd.spawn();
  console.log(`Server started with PID: ${runningProcess.pid}`);
}

2. バックエンドから作成する (Rust)

プロセスを維持管理するためには、tauri::State を使用して Child プロセスを保持する必要があります。

State の定義と登録 (lib.rs / main.rs)

use std::process::Child;
use std::sync::Mutex;

// プロセスを保持するための構造体
// この構造体を .manage() で登録し忘れると "state not managed" エラーになります
struct ProcessState(Mutex<Option<Child>>);

#[tauri::command]
fn start_server(state: tauri::State<'_, ProcessState>) -> Result<String, String> {
    let mut lock = state.0.lock().unwrap();
    
    if lock.is_some() {
        return Err("Server already running".into());
    }

    // std::process::Command は権限設定なしで実行可能です(バックエンド側)
    let child = std::process::Command::new("python")
        .args(["-m", "http.server", "8080"])
        .spawn()
        .map_err(|e| e.to_string())?;

    let pid = child.id();
    *lock = Some(child);
    
    Ok(format!("Started with PID: {}", pid))
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init()) // フロントエンドのサンプルを動作させるために必要
        .manage(ProcessState(Mutex::new(None))) // ★重要: ここで State を初期化・登録します
        .invoke_handler(tauri::generate_handler![start_server])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}