diff --git a/src-tauri/src/cmd/rclone_core.rs b/src-tauri/src/cmd/rclone_core.rs index 25b51cf..e729e40 100644 --- a/src-tauri/src/cmd/rclone_core.rs +++ b/src-tauri/src/cmd/rclone_core.rs @@ -114,7 +114,7 @@ async fn is_rclone_running() -> bool { let response = client .get(format!("{RCLONE_API_BASE}/")) - .timeout(Duration::from_secs(1)) + .timeout(Duration::from_secs(3)) .send() .await; diff --git a/src-tauri/src/cmd/rclone_mount.rs b/src-tauri/src/cmd/rclone_mount.rs index dce139e..485afc9 100644 --- a/src-tauri/src/cmd/rclone_mount.rs +++ b/src-tauri/src/cmd/rclone_mount.rs @@ -2,7 +2,8 @@ use std::fs; use std::path::Path; use reqwest::Client; -use serde_json::json; +use serde::de::DeserializeOwned; +use serde_json::{Value, json}; use tauri::State; 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::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( + &self, + endpoint: &str, + body: Option, + ) -> Result { + 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::() + .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 { + 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] pub async fn rclone_list_config( remote_type: String, _state: State<'_, AppState>, -) -> Result { - let client = Client::new(); - let response = client - .post(format!("{RCLONE_API_BASE}/config/dump")) - .header("Authorization", RCLONE_AUTH) - .send() - .await - .map_err(|e| format!("Failed to send request: {e}"))?; - if response.status().is_success() { - let response_text = response - .text() - .await - .map_err(|e| format!("Failed to read response text: {e}"))?; - let json: serde_json::Value = serde_json::from_str(&response_text) - .map_err(|e| format!("Failed to parse JSON: {e}"))?; - let remotes = if remote_type.is_empty() { - json.clone() - } else if let Some(obj) = json.as_object() { - let mut filtered_map = serde_json::Map::new(); - for (remote_name, remote_config) in obj { - if let Some(config_obj) = remote_config.as_object() - && 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() - )) - } +) -> Result { + let api = RcloneApi::new(); + let text = api.post_text("config/dump").await?; + let all: Value = serde_json::from_str(&text).map_err(|e| format!("Invalid JSON: {}", e))?; + let remotes = match (remote_type.as_str(), all.as_object()) { + ("", _) => all.clone(), + (t, Some(map)) => { + let filtered = map + .iter() + .filter_map(|(name, cfg)| { + cfg.get("type") + .and_then(Value::as_str) + .filter(|&ty| ty == t) + .map(|_| (name.clone(), cfg.clone())) + }) + .collect(); + Value::Object(filtered) + } + _ => Value::Object(Default::default()), + }; + Ok(remotes) } #[tauri::command] pub async fn rclone_list_remotes() -> Result, String> { - let client = Client::new(); - - let response = client - .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}")) - } + let api = RcloneApi::new(); + let resp: RcloneRemoteListResponse = api.post_json("config/listremotes", None).await?; + Ok(resp.remotes) } #[tauri::command] pub async fn rclone_list_mounts() -> Result { - let client = Client::new(); - - 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}")) - } + let api = RcloneApi::new(); + api.post_json("mount/listmounts", None).await } #[tauri::command] @@ -120,37 +120,15 @@ pub async fn rclone_create_remote( config: RcloneWebdavConfig, _state: State<'_, AppState>, ) -> Result { - let client = Client::new(); - - let create_request = RcloneCreateRemoteRequest { - name: name.clone(), - r#type: r#type.clone(), - parameters: crate::conf::rclone::RcloneWebdavConfig { - url: config.url.clone(), - vendor: config.vendor.clone(), - user: config.user.clone(), - pass: config.pass.clone(), - }, + let api = RcloneApi::new(); + let req = RcloneCreateRemoteRequest { + name, + r#type, + parameters: config, }; - - let response = client - .post(format!("{RCLONE_API_BASE}/config/create")) - .header("Authorization", RCLONE_AUTH) - .header("Content-Type", "application/json") - .json(&create_request) - .send() + api.post_json::("config/create", Some(json!(req))) .await - .map_err(|e| format!("Failed to create remote config: {e}"))?; - - 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}")) - } + .map(|_| true) } #[tauri::command] @@ -160,26 +138,11 @@ pub async fn rclone_update_remote( config: RcloneWebdavConfig, _state: State<'_, AppState>, ) -> Result { - let client = Client::new(); - - let response = client - .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() + let api = RcloneApi::new(); + let body = json!({ "name": name, "type": r#type, "parameters": config }); + api.post_json::("config/update", Some(body)) .await - .map_err(|e| format!("Failed to update remote config: {e}"))?; - - 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}")) - } + .map(|_| true) } #[tauri::command] @@ -187,26 +150,11 @@ pub async fn rclone_delete_remote( name: String, _state: State<'_, AppState>, ) -> Result { - let client = Client::new(); - - let response = client - .post(format!("{RCLONE_API_BASE}/config/delete")) - .header("Authorization", RCLONE_AUTH) - .header("Content-Type", "application/json") - .json(&json!({ "name": name })) - .send() + let api = RcloneApi::new(); + let body = json!({ "name": name }); + api.post_json::("config/delete", Some(body)) .await - .map_err(|e| format!("Failed to delete remote config: {e}"))?; - - 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}")) - } + .map(|_| true) } #[tauri::command] @@ -214,26 +162,10 @@ pub async fn rclone_mount_remote( mount_request: RcloneMountRequest, _state: State<'_, AppState>, ) -> Result { - let client = Client::new(); - - let response = client - .post(format!("{RCLONE_API_BASE}/mount/mount")) - .header("Authorization", RCLONE_AUTH) - .header("Content-Type", "application/json") - .json(&mount_request) - .send() + let api = RcloneApi::new(); + api.post_json::("mount/mount", Some(json!(mount_request))) .await - .map_err(|e| format!("Failed to mount remote: {e}"))?; - - 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}")) - } + .map(|_| true) } #[tauri::command] @@ -241,26 +173,10 @@ pub async fn rclone_unmount_remote( mount_point: String, _state: State<'_, AppState>, ) -> Result { - let client = Client::new(); - - 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() + let api = RcloneApi::new(); + api.post_json::("mount/unmount", Some(json!({ "mountPoint": mount_point }))) .await - .map_err(|e| format!("Failed to unmount remote: {e}"))?; - - 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}")) - } + .map(|_| true) } #[tauri::command] diff --git a/src-tauri/src/utils/path.rs b/src-tauri/src/utils/path.rs index ab4255c..92c169f 100644 --- a/src-tauri/src/utils/path.rs +++ b/src-tauri/src/utils/path.rs @@ -16,48 +16,31 @@ fn get_app_dir() -> Result { Ok(app_dir) } -pub fn get_openlist_binary_path() -> Result { - let app_dir = get_app_dir()?; - - let binary_name = if cfg!(target_os = "windows") { - "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:?}" - )); +fn get_binary_path(binary: &str, service_name: &str) -> Result { + let mut name = binary.to_string(); + if cfg!(target_os = "windows") { + name.push_str(".exe"); } - 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 { + get_binary_path("openlist", "OpenList") } pub fn get_rclone_binary_path() -> Result { - let app_dir = get_app_dir()?; - - 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) + get_binary_path("rclone", "Rclone") } pub fn get_app_config_dir() -> Result { - let app_dir = get_app_dir()?; - fs::create_dir_all(&app_dir).map_err(|e| e.to_string())?; - Ok(app_dir) + get_app_dir() } pub fn app_config_file_path() -> Result { @@ -65,8 +48,7 @@ pub fn app_config_file_path() -> Result { } pub fn get_app_logs_dir() -> Result { - let app_dir = get_app_dir()?; - let logs_dir = app_dir.join("logs"); - fs::create_dir_all(&logs_dir).map_err(|e| e.to_string())?; - Ok(logs_dir) + let logs = get_app_dir()?.join("logs"); + fs::create_dir_all(&logs).map_err(|e| e.to_string())?; + Ok(logs) }