From d2c834d9365107a2a025681313dccece7077c37c Mon Sep 17 00:00:00 2001 From: Kuingsmile <96409857+Kuingsmile@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:28:00 +0800 Subject: [PATCH] :sparkles: Feature(custom): add rclone api port setting ISSUES CLOSED: #73 --- .vscode/settings.json | 1 + README.md | 4 ++- README_en.md | 6 ++-- src-tauri/src/cmd/config.rs | 38 ++++++++++++++++++++-- src-tauri/src/cmd/rclone_core.rs | 12 ++++--- src-tauri/src/cmd/rclone_mount.rs | 52 +++++++++++++++++++------------ src-tauri/src/conf/rclone.rs | 2 ++ src/i18n/locales/en.json | 9 ++++++ src/i18n/locales/zh.json | 9 ++++++ src/stores/app.ts | 5 +-- src/types/types.d.ts | 1 + src/views/SettingsView.vue | 30 +++++++++++++++++- 12 files changed, 137 insertions(+), 32 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b830217..def5d96 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -35,6 +35,7 @@ "src/i18n", "src/i18n/locales" ], + "i18n-ally.keystyle": "nested", // "rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu" //"rust-analyzer.cargo.target": "x86_64-pc-windows-msvc" // "rust-analyzer.cargo.target": "x86_64-apple-darwin", diff --git a/README.md b/README.md index 78789f2..0e833b2 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,6 @@ winget install OpenListTeam.OpenListDesktop "data_dir": "", "auto_launch": true, "ssl_enabled": false, - "admin_password": "" } } ``` @@ -266,6 +265,7 @@ winget install OpenListTeam.OpenListDesktop "extraFlags": ["--vfs-cache-mode", "full"] } }, + "api_port": 45572 } } ``` @@ -280,6 +280,8 @@ winget install OpenListTeam.OpenListDesktop "gh_proxy": "https://ghproxy.com/", "gh_proxy_api": false, "open_links_in_browser": true, + "admin_password": "", + "show_window_on_startup": false } } ``` diff --git a/README_en.md b/README_en.md index ebfeca2..63c58d2 100644 --- a/README_en.md +++ b/README_en.md @@ -244,8 +244,7 @@ Add custom Rclone flags for optimal performance: "port": 5244, "data_dir": "", "auto_launch": true, - "ssl_enabled": false, - "admin_password": "" + "ssl_enabled": false } } ``` @@ -266,6 +265,7 @@ Add custom Rclone flags for optimal performance: "extraFlags": ["--vfs-cache-mode", "full"] } }, + "api_port": 45572 } } ``` @@ -280,6 +280,8 @@ Add custom Rclone flags for optimal performance: "gh_proxy": "https://ghproxy.com/", "gh_proxy_api": false, "open_links_in_browser": true, + "admin_password": "", + "show_window_on_startup": false } } ``` diff --git a/src-tauri/src/cmd/config.rs b/src-tauri/src/cmd/config.rs index 86e0305..e922cf3 100644 --- a/src-tauri/src/cmd/config.rs +++ b/src-tauri/src/cmd/config.rs @@ -6,6 +6,7 @@ use tokio::time::{Duration, sleep}; use crate::cmd::http_api::{delete_process, get_process_list, start_process, stop_process}; use crate::cmd::openlist_core::create_openlist_core_process; +use crate::cmd::rclone_core::create_rclone_backend_process; use crate::conf::config::MergedSettings; use crate::object::structs::AppState; use crate::utils::path::{app_config_file_path, get_default_openlist_data_dir}; @@ -88,6 +89,23 @@ async fn recreate_openlist_core_process(state: State<'_, AppState>) -> Result<() Ok(()) } +async fn recreate_rclone_backend_process(state: State<'_, AppState>) -> Result<(), String> { + let procs = get_process_list(state.clone()).await?; + if let Some(proc) = procs + .into_iter() + .find(|p| p.config.name == "single_rclone_backend_process") + { + let id = proc.config.id.clone(); + let _ = stop_process(id.clone(), state.clone()).await; + sleep(Duration::from_millis(1000)).await; + let _ = delete_process(id, state.clone()).await; + sleep(Duration::from_millis(1000)).await; + + create_rclone_backend_process(state.clone()).await?; + } + Ok(()) +} + #[tauri::command] pub async fn load_settings(state: State<'_, AppState>) -> Result, String> { state.load_settings()?; @@ -111,12 +129,18 @@ pub async fn save_settings_with_update_port( state: State<'_, AppState>, ) -> Result { let old_settings = state.get_settings(); - let needs_process_recreation = if let Some(old) = old_settings { + let needs_openlist_recreation = if let Some(old) = &old_settings { old.openlist.data_dir != settings.openlist.data_dir } else { false }; + let needs_rclone_recreation = if let Some(old) = &old_settings { + old.rclone.api_port != settings.rclone.api_port + } else { + false + }; + state.update_settings(settings.clone()); persist_app_settings(&settings)?; let data_dir = if settings.openlist.data_dir.is_empty() { @@ -126,7 +150,7 @@ pub async fn save_settings_with_update_port( }; update_data_config(settings.openlist.port, data_dir)?; - if needs_process_recreation { + if needs_openlist_recreation { if let Err(e) = recreate_openlist_core_process(state.clone()).await { log::error!("{e}"); return Err(e); @@ -142,6 +166,16 @@ pub async fn save_settings_with_update_port( log::info!("Settings saved and OpenList core restarted with new port successfully"); } + if needs_rclone_recreation { + if let Err(e) = recreate_rclone_backend_process(state.clone()).await { + log::error!("Failed to recreate rclone backend process: {e}"); + return Err(format!("Failed to recreate rclone backend process: {e}")); + } + log::info!("Rclone backend process recreated with new API port successfully"); + } else { + log::info!("Settings saved successfully (no rclone port change detected)"); + } + Ok(true) } diff --git a/src-tauri/src/cmd/rclone_core.rs b/src-tauri/src/cmd/rclone_core.rs index 1415b66..ac10ccd 100644 --- a/src-tauri/src/cmd/rclone_core.rs +++ b/src-tauri/src/cmd/rclone_core.rs @@ -7,8 +7,6 @@ use crate::object::structs::AppState; use crate::utils::api::{CreateProcessResponse, ProcessConfig, get_api_key, get_server_port}; use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path, get_rclone_config_path}; -// use 45572 due to the reserved port on Windows -pub const RCLONE_API_BASE: &str = "http://127.0.0.1:45572"; // admin:admin base64 encoded pub const RCLONE_AUTH: &str = "Basic YWRtaW46YWRtaW4="; @@ -33,7 +31,7 @@ pub async fn create_and_start_rclone_backend( #[tauri::command] pub async fn create_rclone_backend_process( - _state: State<'_, AppState>, + state: State<'_, AppState>, ) -> Result { let binary_path = get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {e}"))?; @@ -44,6 +42,12 @@ pub async fn create_rclone_backend_process( let log_file_path = log_file_path.join("process_rclone.log"); let api_key = get_api_key(); let port = get_server_port(); + + let rclone_port = state + .get_settings() + .map(|settings| settings.rclone.api_port) + .unwrap_or(45572); + let config = ProcessConfig { id: "rclone_backend".into(), name: "single_rclone_backend_process".into(), @@ -57,7 +61,7 @@ pub async fn create_rclone_backend_process( "--rc-pass".into(), "admin".into(), "--rc-addr".into(), - format!("127.0.0.1:45572"), + format!("127.0.0.1:{}", rclone_port), "--rc-web-gui-no-open-browser".into(), ], log_file: log_file_path.to_string_lossy().into_owned(), diff --git a/src-tauri/src/cmd/rclone_mount.rs b/src-tauri/src/cmd/rclone_mount.rs index 0be31bb..19b9289 100644 --- a/src-tauri/src/cmd/rclone_mount.rs +++ b/src-tauri/src/cmd/rclone_mount.rs @@ -7,7 +7,7 @@ use serde_json::{Value, json}; use tauri::State; use super::http_api::get_process_list; -use super::rclone_core::{RCLONE_API_BASE, RCLONE_AUTH}; +use super::rclone_core::RCLONE_AUTH; use crate::conf::rclone::{RcloneCreateRemoteRequest, RcloneMountRequest, RcloneWebdavConfig}; use crate::object::structs::{ AppState, RcloneMountInfo, RcloneMountListResponse, RcloneRemoteListResponse, @@ -16,14 +16,24 @@ 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, get_rclone_config_path}; +fn get_rclone_api_base_url(state: &State) -> String { + let port = state + .get_settings() + .map(|settings| settings.rclone.api_port) + .unwrap_or(45572); + format!("http://127.0.0.1:{}", port) +} + struct RcloneApi { client: Client, + api_base: String, } impl RcloneApi { - fn new() -> Self { + fn new(api_base: String) -> Self { Self { client: Client::new(), + api_base, } } @@ -32,7 +42,7 @@ impl RcloneApi { endpoint: &str, body: Option, ) -> Result { - let url = format!("{RCLONE_API_BASE}/{endpoint}"); + let url = format!("{}/{endpoint}", self.api_base); 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"); @@ -53,7 +63,7 @@ impl RcloneApi { } async fn post_text(&self, endpoint: &str) -> Result { - let url = format!("{RCLONE_API_BASE}/{endpoint}"); + let url = format!("{}/{endpoint}", self.api_base); let resp = self .client .post(&url) @@ -76,9 +86,9 @@ impl RcloneApi { #[tauri::command] pub async fn rclone_list_config( remote_type: String, - _state: State<'_, AppState>, + state: State<'_, AppState>, ) -> Result { - let api = RcloneApi::new(); + let api = RcloneApi::new(get_rclone_api_base_url(&state)); 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()) { @@ -101,15 +111,17 @@ pub async fn rclone_list_config( } #[tauri::command] -pub async fn rclone_list_remotes() -> Result, String> { - let api = RcloneApi::new(); +pub async fn rclone_list_remotes(state: State<'_, AppState>) -> Result, String> { + let api = RcloneApi::new(get_rclone_api_base_url(&state)); let resp: RcloneRemoteListResponse = api.post_json("config/listremotes", None).await?; Ok(resp.remotes) } #[tauri::command] -pub async fn rclone_list_mounts() -> Result { - let api = RcloneApi::new(); +pub async fn rclone_list_mounts( + state: State<'_, AppState>, +) -> Result { + let api = RcloneApi::new(get_rclone_api_base_url(&state)); api.post_json("mount/listmounts", None).await } @@ -118,9 +130,9 @@ pub async fn rclone_create_remote( name: String, r#type: String, config: RcloneWebdavConfig, - _state: State<'_, AppState>, + state: State<'_, AppState>, ) -> Result { - let api = RcloneApi::new(); + let api = RcloneApi::new(get_rclone_api_base_url(&state)); let req = RcloneCreateRemoteRequest { name, r#type, @@ -136,9 +148,9 @@ pub async fn rclone_update_remote( name: String, r#type: String, config: RcloneWebdavConfig, - _state: State<'_, AppState>, + state: State<'_, AppState>, ) -> Result { - let api = RcloneApi::new(); + let api = RcloneApi::new(get_rclone_api_base_url(&state)); let body = json!({ "name": name, "type": r#type, "parameters": config }); api.post_json::("config/update", Some(body)) .await @@ -148,9 +160,9 @@ pub async fn rclone_update_remote( #[tauri::command] pub async fn rclone_delete_remote( name: String, - _state: State<'_, AppState>, + state: State<'_, AppState>, ) -> Result { - let api = RcloneApi::new(); + let api = RcloneApi::new(get_rclone_api_base_url(&state)); let body = json!({ "name": name }); api.post_json::("config/delete", Some(body)) .await @@ -160,9 +172,9 @@ pub async fn rclone_delete_remote( #[tauri::command] pub async fn rclone_mount_remote( mount_request: RcloneMountRequest, - _state: State<'_, AppState>, + state: State<'_, AppState>, ) -> Result { - let api = RcloneApi::new(); + let api = RcloneApi::new(get_rclone_api_base_url(&state)); api.post_json::("mount/mount", Some(json!(mount_request))) .await .map(|_| true) @@ -171,9 +183,9 @@ pub async fn rclone_mount_remote( #[tauri::command] pub async fn rclone_unmount_remote( mount_point: String, - _state: State<'_, AppState>, + state: State<'_, AppState>, ) -> Result { - let api = RcloneApi::new(); + let api = RcloneApi::new(get_rclone_api_base_url(&state)); api.post_json::("mount/unmount", Some(json!({ "mountPoint": mount_point }))) .await .map(|_| true) diff --git a/src-tauri/src/conf/rclone.rs b/src-tauri/src/conf/rclone.rs index 0b8a423..4f416f2 100644 --- a/src-tauri/src/conf/rclone.rs +++ b/src-tauri/src/conf/rclone.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct RcloneConfig { pub config: serde_json::Value, + pub api_port: u16, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -45,6 +46,7 @@ impl RcloneConfig { pub fn new() -> Self { Self { config: serde_json::Value::Object(Default::default()), + api_port: 45572, } } } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 43607e5..e36877e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -178,6 +178,15 @@ }, "rclone": { "subtitle": "Configure remote storage connections", + "api": { + "title": "API Configuration", + "subtitle": "Configure the Rclone API server settings", + "port": { + "label": "API Port", + "placeholder": "45572", + "help": "Port number for the Rclone API server (1-65535). Default: 45572" + } + }, "config": { "title": "Remote Storage", "subtitle": "Configure rclone for remote storage access", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index da46dba..387cf20 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -178,6 +178,15 @@ }, "rclone": { "subtitle": "配置远程存储连接", + "api": { + "title": "API 配置", + "subtitle": "配置 Rclone API 服务器设置", + "port": { + "label": "API 端口", + "placeholder": "45572", + "help": "Rclone API 服务器的端口号 (1-65535)。默认:45572" + } + }, "config": { "title": "远程存储", "subtitle": "配置 rclone 远程存储访问", diff --git a/src/stores/app.ts b/src/stores/app.ts index 253a3f1..8824546 100644 --- a/src/stores/app.ts +++ b/src/stores/app.ts @@ -8,14 +8,15 @@ type ActionFn = () => Promise export const useAppStore = defineStore('app', () => { const settings = ref({ openlist: { port: 5244, data_dir: '', auto_launch: false, ssl_enabled: false }, - rclone: { config: {} }, + rclone: { config: {}, api_port: 45572 }, app: { theme: 'light', auto_update_enabled: true, gh_proxy: '', gh_proxy_api: false, open_links_in_browser: false, - admin_password: undefined + admin_password: undefined, + show_window_on_startup: false } }) const openlistCoreStatus = ref({ running: false }) diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 269767d..f227955 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -13,6 +13,7 @@ interface OpenListCoreConfig { interface RcloneConfig { config?: any // Flexible JSON object for rclone configuration + api_port: number // Port for the Rclone API server } interface RcloneWebdavConfig { diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue index b3e6ff1..9af2fab 100644 --- a/src/views/SettingsView.vue +++ b/src/views/SettingsView.vue @@ -41,6 +41,7 @@ const rcloneSettings = reactive({ ...appStore.settings.rclone }) const appSettings = reactive({ ...appStore.settings.app }) let originalOpenlistPort = openlistCoreSettings.port || 5244 let originalDataDir = openlistCoreSettings.data_dir +let originalRcloneApiPort = rcloneSettings.api_port || 45572 let originalAdminPassword = appStore.settings.app.admin_password || '' watch(autoStartApp, async newValue => { @@ -85,6 +86,7 @@ onMounted(async () => { if (openlistCoreSettings.ssl_enabled === undefined) openlistCoreSettings.ssl_enabled = false if (!rcloneSettings.config) rcloneSettings.config = {} + if (!rcloneSettings.api_port) rcloneSettings.api_port = 45572 rcloneConfigJson.value = JSON.stringify(rcloneSettings.config, null, 2) if (!appSettings.theme) appSettings.theme = 'light' @@ -97,6 +99,7 @@ onMounted(async () => { if (!appSettings.admin_password) appSettings.admin_password = '' originalOpenlistPort = openlistCoreSettings.port || 5244 originalDataDir = openlistCoreSettings.data_dir + originalRcloneApiPort = rcloneSettings.api_port || 45572 // Load current admin password await loadCurrentAdminPassword() @@ -139,7 +142,11 @@ const handleSave = async () => { const needsPasswordUpdate = originalAdminPassword !== appSettings.admin_password && appSettings.admin_password - if (originalOpenlistPort !== openlistCoreSettings.port || originalDataDir !== openlistCoreSettings.data_dir) { + if ( + originalOpenlistPort !== openlistCoreSettings.port || + originalDataDir !== openlistCoreSettings.data_dir || + originalRcloneApiPort !== rcloneSettings.api_port + ) { await appStore.saveSettingsWithCoreUpdate() } else { await appStore.saveSettings() @@ -161,6 +168,7 @@ const handleSave = async () => { } originalOpenlistPort = openlistCoreSettings.port || 5244 + originalRcloneApiPort = rcloneSettings.api_port || 45572 originalDataDir = openlistCoreSettings.data_dir } catch (error) { message.value = t('settings.saveFailed') @@ -463,6 +471,26 @@ const loadCurrentAdminPassword = async () => {
+
+

{{ t('settings.rclone.api.title') }}

+

{{ t('settings.rclone.api.subtitle') }}

+ +
+
+ + + {{ t('settings.rclone.api.port.help') }} +
+
+
+

{{ t('settings.rclone.config.title') }}

{{ t('settings.rclone.config.subtitle') }}