refactor: increase timeout for rclone API requests and refactor some files

This commit is contained in:
Kuingsmile
2025-07-10 10:51:10 +08:00
parent 64b12e6c3b
commit 23095dba6b
3 changed files with 127 additions and 229 deletions

View File

@@ -114,7 +114,7 @@ async fn is_rclone_running() -> bool {
let response = client let response = client
.get(format!("{RCLONE_API_BASE}/")) .get(format!("{RCLONE_API_BASE}/"))
.timeout(Duration::from_secs(1)) .timeout(Duration::from_secs(3))
.send() .send()
.await; .await;

View File

@@ -2,7 +2,8 @@ use std::fs;
use std::path::Path; use std::path::Path;
use reqwest::Client; use reqwest::Client;
use serde_json::json; use serde::de::DeserializeOwned;
use serde_json::{Value, json};
use tauri::State; use tauri::State;
use super::http_api::get_process_list; use super::http_api::get_process_list;
@@ -15,102 +16,101 @@ use crate::utils::api::{CreateProcessResponse, ProcessConfig, get_api_key, get_s
use crate::utils::args::split_args_vec; use crate::utils::args::split_args_vec;
use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path}; use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path};
struct RcloneApi {
client: Client,
}
impl RcloneApi {
fn new() -> Self {
Self {
client: Client::new(),
}
}
async fn post_json<T: DeserializeOwned>(
&self,
endpoint: &str,
body: Option<Value>,
) -> Result<T, String> {
let url = format!("{RCLONE_API_BASE}/{endpoint}");
let mut req = self.client.post(&url).header("Authorization", RCLONE_AUTH);
if let Some(b) = body {
req = req.json(&b).header("Content-Type", "application/json");
}
let resp = req
.send()
.await
.map_err(|e| format!("Request failed: {e}"))?;
let status = resp.status();
if status.is_success() {
resp.json::<T>()
.await
.map_err(|e| format!("Failed to parse JSON: {e}"))
} else {
let txt = resp.text().await.unwrap_or_default();
Err(format!("API error {status}: {txt}"))
}
}
async fn post_text(&self, endpoint: &str) -> Result<String, String> {
let url = format!("{RCLONE_API_BASE}/{endpoint}");
let resp = self
.client
.post(&url)
.header("Authorization", RCLONE_AUTH)
.send()
.await
.map_err(|e| format!("Request failed: {e}"))?;
let status = resp.status();
if status.is_success() {
resp.text()
.await
.map_err(|e| format!("Failed to read text: {e}"))
} else {
let txt = resp.text().await.unwrap_or_default();
Err(format!("API error {status}: {txt}"))
}
}
}
#[tauri::command] #[tauri::command]
pub async fn rclone_list_config( pub async fn rclone_list_config(
remote_type: String, remote_type: String,
_state: State<'_, AppState>, _state: State<'_, AppState>,
) -> Result<serde_json::Value, String> { ) -> Result<Value, String> {
let client = Client::new(); let api = RcloneApi::new();
let response = client let text = api.post_text("config/dump").await?;
.post(format!("{RCLONE_API_BASE}/config/dump")) let all: Value = serde_json::from_str(&text).map_err(|e| format!("Invalid JSON: {}", e))?;
.header("Authorization", RCLONE_AUTH) let remotes = match (remote_type.as_str(), all.as_object()) {
.send() ("", _) => all.clone(),
.await (t, Some(map)) => {
.map_err(|e| format!("Failed to send request: {e}"))?; let filtered = map
if response.status().is_success() { .iter()
let response_text = response .filter_map(|(name, cfg)| {
.text() cfg.get("type")
.await .and_then(Value::as_str)
.map_err(|e| format!("Failed to read response text: {e}"))?; .filter(|&ty| ty == t)
let json: serde_json::Value = serde_json::from_str(&response_text) .map(|_| (name.clone(), cfg.clone()))
.map_err(|e| format!("Failed to parse JSON: {e}"))?; })
let remotes = if remote_type.is_empty() { .collect();
json.clone() Value::Object(filtered)
} else if let Some(obj) = json.as_object() { }
let mut filtered_map = serde_json::Map::new(); _ => Value::Object(Default::default()),
for (remote_name, remote_config) in obj { };
if let Some(config_obj) = remote_config.as_object() Ok(remotes)
&& let Some(remote_type_value) = config_obj.get("type")
&& let Some(type_str) = remote_type_value.as_str()
&& type_str == remote_type
{
filtered_map.insert(remote_name.clone(), remote_config.clone());
}
}
serde_json::Value::Object(filtered_map)
} else {
serde_json::Value::Object(serde_json::Map::new())
};
Ok(remotes)
} else {
Err(format!(
"Failed to list Rclone config: {}",
response.status()
))
}
} }
#[tauri::command] #[tauri::command]
pub async fn rclone_list_remotes() -> Result<Vec<String>, String> { pub async fn rclone_list_remotes() -> Result<Vec<String>, String> {
let client = Client::new(); let api = RcloneApi::new();
let resp: RcloneRemoteListResponse = api.post_json("config/listremotes", None).await?;
let response = client Ok(resp.remotes)
.post(format!("{RCLONE_API_BASE}/config/listremotes"))
.header("Authorization", RCLONE_AUTH)
.send()
.await
.map_err(|e| format!("Failed to list remotes: {e}"))?;
if response.status().is_success() {
let remote_list: RcloneRemoteListResponse = response
.json()
.await
.map_err(|e| format!("Failed to parse remote list response: {e}"))?;
Ok(remote_list.remotes)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to list remotes: {error_text}"))
}
} }
#[tauri::command] #[tauri::command]
pub async fn rclone_list_mounts() -> Result<RcloneMountListResponse, String> { pub async fn rclone_list_mounts() -> Result<RcloneMountListResponse, String> {
let client = Client::new(); let api = RcloneApi::new();
api.post_json("mount/listmounts", None).await
let response = client
.post(format!("{RCLONE_API_BASE}/mount/listmounts"))
.header("Authorization", RCLONE_AUTH)
.send()
.await
.map_err(|e| format!("Failed to list mounts: {e}"))?;
if response.status().is_success() {
let mount_list: RcloneMountListResponse = response
.json()
.await
.map_err(|e| format!("Failed to parse mount list response: {e}"))?;
Ok(mount_list)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to list mounts: {error_text}"))
}
} }
#[tauri::command] #[tauri::command]
@@ -120,37 +120,15 @@ pub async fn rclone_create_remote(
config: RcloneWebdavConfig, config: RcloneWebdavConfig,
_state: State<'_, AppState>, _state: State<'_, AppState>,
) -> Result<bool, String> { ) -> Result<bool, String> {
let client = Client::new(); let api = RcloneApi::new();
let req = RcloneCreateRemoteRequest {
let create_request = RcloneCreateRemoteRequest { name,
name: name.clone(), r#type,
r#type: r#type.clone(), parameters: config,
parameters: crate::conf::rclone::RcloneWebdavConfig {
url: config.url.clone(),
vendor: config.vendor.clone(),
user: config.user.clone(),
pass: config.pass.clone(),
},
}; };
api.post_json::<Value>("config/create", Some(json!(req)))
let response = client
.post(format!("{RCLONE_API_BASE}/config/create"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&create_request)
.send()
.await .await
.map_err(|e| format!("Failed to create remote config: {e}"))?; .map(|_| true)
if response.status().is_success() {
Ok(true)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to create remote config: {error_text}"))
}
} }
#[tauri::command] #[tauri::command]
@@ -160,26 +138,11 @@ pub async fn rclone_update_remote(
config: RcloneWebdavConfig, config: RcloneWebdavConfig,
_state: State<'_, AppState>, _state: State<'_, AppState>,
) -> Result<bool, String> { ) -> Result<bool, String> {
let client = Client::new(); let api = RcloneApi::new();
let body = json!({ "name": name, "type": r#type, "parameters": config });
let response = client api.post_json::<Value>("config/update", Some(body))
.post(format!("{RCLONE_API_BASE}/config/update"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&json!({ "name": name, "type": r#type, "parameters": config }))
.send()
.await .await
.map_err(|e| format!("Failed to update remote config: {e}"))?; .map(|_| true)
if response.status().is_success() {
Ok(true)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to update remote config: {error_text}"))
}
} }
#[tauri::command] #[tauri::command]
@@ -187,26 +150,11 @@ pub async fn rclone_delete_remote(
name: String, name: String,
_state: State<'_, AppState>, _state: State<'_, AppState>,
) -> Result<bool, String> { ) -> Result<bool, String> {
let client = Client::new(); let api = RcloneApi::new();
let body = json!({ "name": name });
let response = client api.post_json::<Value>("config/delete", Some(body))
.post(format!("{RCLONE_API_BASE}/config/delete"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&json!({ "name": name }))
.send()
.await .await
.map_err(|e| format!("Failed to delete remote config: {e}"))?; .map(|_| true)
if response.status().is_success() {
Ok(true)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to delete remote config: {error_text}"))
}
} }
#[tauri::command] #[tauri::command]
@@ -214,26 +162,10 @@ pub async fn rclone_mount_remote(
mount_request: RcloneMountRequest, mount_request: RcloneMountRequest,
_state: State<'_, AppState>, _state: State<'_, AppState>,
) -> Result<bool, String> { ) -> Result<bool, String> {
let client = Client::new(); let api = RcloneApi::new();
api.post_json::<Value>("mount/mount", Some(json!(mount_request)))
let response = client
.post(format!("{RCLONE_API_BASE}/mount/mount"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&mount_request)
.send()
.await .await
.map_err(|e| format!("Failed to mount remote: {e}"))?; .map(|_| true)
if response.status().is_success() {
Ok(true)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to mount remote: {error_text}"))
}
} }
#[tauri::command] #[tauri::command]
@@ -241,26 +173,10 @@ pub async fn rclone_unmount_remote(
mount_point: String, mount_point: String,
_state: State<'_, AppState>, _state: State<'_, AppState>,
) -> Result<bool, String> { ) -> Result<bool, String> {
let client = Client::new(); let api = RcloneApi::new();
api.post_json::<Value>("mount/unmount", Some(json!({ "mountPoint": mount_point })))
let response = client
.post(format!("{RCLONE_API_BASE}/mount/unmount"))
.header("Authorization", RCLONE_AUTH)
.header("Content-Type", "application/json")
.json(&json!({ "mountPoint": mount_point }))
.send()
.await .await
.map_err(|e| format!("Failed to unmount remote: {e}"))?; .map(|_| true)
if response.status().is_success() {
Ok(true)
} else {
let error_text = response
.text()
.await
.unwrap_or_else(|_| "Unknown error".to_string());
Err(format!("Failed to unmount remote: {error_text}"))
}
} }
#[tauri::command] #[tauri::command]

View File

@@ -16,48 +16,31 @@ fn get_app_dir() -> Result<PathBuf, String> {
Ok(app_dir) Ok(app_dir)
} }
pub fn get_openlist_binary_path() -> Result<PathBuf, String> { fn get_binary_path(binary: &str, service_name: &str) -> Result<PathBuf, String> {
let app_dir = get_app_dir()?; let mut name = binary.to_string();
if cfg!(target_os = "windows") {
let binary_name = if cfg!(target_os = "windows") { name.push_str(".exe");
"openlist.exe"
} else {
"openlist"
};
let binary_path = app_dir.join(binary_name);
if !binary_path.exists() {
return Err(format!(
"OpenList service binary not found at: {binary_path:?}"
));
} }
Ok(binary_path) let path = get_app_dir()?.join(&name);
if !path.exists() {
return Err(format!(
"{service_name} service binary not found at: {path:?}"
));
}
Ok(path)
}
pub fn get_openlist_binary_path() -> Result<PathBuf, String> {
get_binary_path("openlist", "OpenList")
} }
pub fn get_rclone_binary_path() -> Result<PathBuf, String> { pub fn get_rclone_binary_path() -> Result<PathBuf, String> {
let app_dir = get_app_dir()?; get_binary_path("rclone", "Rclone")
let binary_name = if cfg!(target_os = "windows") {
"rclone.exe"
} else {
"rclone"
};
let binary_path = app_dir.join(binary_name);
if !binary_path.exists() {
return Err(format!(
"Rclone service binary not found at: {binary_path:?}"
));
}
Ok(binary_path)
} }
pub fn get_app_config_dir() -> Result<PathBuf, String> { pub fn get_app_config_dir() -> Result<PathBuf, String> {
let app_dir = get_app_dir()?; get_app_dir()
fs::create_dir_all(&app_dir).map_err(|e| e.to_string())?;
Ok(app_dir)
} }
pub fn app_config_file_path() -> Result<PathBuf, String> { pub fn app_config_file_path() -> Result<PathBuf, String> {
@@ -65,8 +48,7 @@ pub fn app_config_file_path() -> Result<PathBuf, String> {
} }
pub fn get_app_logs_dir() -> Result<PathBuf, String> { pub fn get_app_logs_dir() -> Result<PathBuf, String> {
let app_dir = get_app_dir()?; let logs = get_app_dir()?.join("logs");
let logs_dir = app_dir.join("logs"); fs::create_dir_all(&logs).map_err(|e| e.to_string())?;
fs::create_dir_all(&logs_dir).map_err(|e| e.to_string())?; Ok(logs)
Ok(logs_dir)
} }