use std::path::PathBuf;
use std::sync::Arc;
use rmcp::ErrorData;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use crate::errors::{McpServerError, map_error};
use crate::state::SessionState;
use crate::tools::actions::AckOutput;
use crate::tools::common::current_tab;
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DownloadInput {
pub url: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct DownloadOutput {
pub ok: bool,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<String>,
}
pub async fn download(
state: Arc<Mutex<SessionState>>,
input: DownloadInput,
) -> Result<DownloadOutput, ErrorData> {
let s = state.lock().await;
let tab = current_tab(&s).await?;
tab.download_file(
input.url.as_str(),
input.filename.as_deref().map(PathBuf::from),
)
.await
.map_err(|e| map_error(McpServerError::from(e)))?;
Ok(DownloadOutput {
ok: true,
url: input.url,
filename: input.filename,
})
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetDownloadPathInput {
pub path: String,
}
pub async fn set_download_path(
state: Arc<Mutex<SessionState>>,
input: SetDownloadPathInput,
) -> Result<AckOutput, ErrorData> {
let s = state.lock().await;
let tab = current_tab(&s).await?;
tab.set_download_path(PathBuf::from(&input.path))
.await
.map_err(|e| map_error(McpServerError::from(e)))?;
Ok(AckOutput { ok: true })
}
#[cfg(test)]
#[allow(clippy::panic, clippy::unwrap_used)]
mod tests {
use super::*;
#[tokio::test]
async fn download_with_no_browser_errors() {
let state = Arc::new(Mutex::new(SessionState::new()));
let err = download(
state,
DownloadInput {
url: "https://example.com/x.bin".into(),
filename: None,
},
)
.await
.expect_err("expected BrowserNotOpen");
assert!(err.message.contains("Browser not open"));
}
}