use std::time::Duration;
use reqwest::Client;
pub async fn add_torrent(
client: &Client,
base: &str,
auth: &str,
magnet: Option<&str>,
torrent_b64: Option<&str>,
) -> anyhow::Result<usize> {
let mut body = serde_json::Map::new();
if let Some(m) = magnet {
body.insert("magnet".into(), serde_json::Value::String(m.to_string()));
}
if let Some(b64) = torrent_b64 {
body.insert("torrent_b64".into(), serde_json::Value::String(b64.to_string()));
}
let v: serde_json::Value = client
.post(format!("{base}/torrents"))
.bearer_auth(auth)
.json(&serde_json::Value::Object(body))
.send()
.await?
.error_for_status()?
.json()
.await?;
v["id"]
.as_u64()
.map(|i| i as usize)
.ok_or_else(|| anyhow::anyhow!("add response missing id"))
}
pub async fn wait_playable(
client: &Client,
base: &str,
auth: &str,
id: usize,
timeout: Duration,
) -> anyhow::Result<String> {
let deadline = tokio::time::Instant::now() + timeout;
loop {
let req = client
.get(format!("{base}/torrents/{id}/play"))
.bearer_auth(auth);
match req.send().await {
Ok(resp) => {
if let Ok(v) = resp.json::<serde_json::Value>().await
&& let Some(url) = v["url"].as_str()
{
return Ok(url.to_string());
}
}
Err(e) if tokio::time::Instant::now() >= deadline => {
anyhow::bail!("no playable file yet ({e})");
}
Err(_) => {}
}
if tokio::time::Instant::now() >= deadline {
anyhow::bail!(
"torrent {id} is not playable after {}s (no peers to fetch metadata from?)",
timeout.as_secs()
);
}
tokio::time::sleep(Duration::from_millis(750)).await;
}
}
pub fn torrent_file_to_b64(path: &std::path::Path) -> anyhow::Result<String> {
use base64::Engine;
let bytes = std::fs::read(path)?;
Ok(base64::engine::general_purpose::STANDARD.encode(bytes))
}