Feature(custom): add rclone api port setting

ISSUES CLOSED: #73
This commit is contained in:
Kuingsmile
2025-08-25 15:28:00 +08:00
parent 31980949a3
commit d2c834d936
12 changed files with 137 additions and 32 deletions

View File

@@ -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",

View File

@@ -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
}
}
```

View File

@@ -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
}
}
```

View File

@@ -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<Option<MergedSettings>, String> {
state.load_settings()?;
@@ -111,12 +129,18 @@ pub async fn save_settings_with_update_port(
state: State<'_, AppState>,
) -> Result<bool, String> {
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)
}

View File

@@ -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<ProcessConfig, String> {
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(),

View File

@@ -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<AppState>) -> 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<Value>,
) -> Result<T, String> {
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<String, String> {
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<Value, String> {
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<Vec<String>, String> {
let api = RcloneApi::new();
pub async fn rclone_list_remotes(state: State<'_, AppState>) -> Result<Vec<String>, 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<RcloneMountListResponse, String> {
let api = RcloneApi::new();
pub async fn rclone_list_mounts(
state: State<'_, AppState>,
) -> Result<RcloneMountListResponse, String> {
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<bool, String> {
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<bool, String> {
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::<Value>("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<bool, String> {
let api = RcloneApi::new();
let api = RcloneApi::new(get_rclone_api_base_url(&state));
let body = json!({ "name": name });
api.post_json::<Value>("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<bool, String> {
let api = RcloneApi::new();
let api = RcloneApi::new(get_rclone_api_base_url(&state));
api.post_json::<Value>("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<bool, String> {
let api = RcloneApi::new();
let api = RcloneApi::new(get_rclone_api_base_url(&state));
api.post_json::<Value>("mount/unmount", Some(json!({ "mountPoint": mount_point })))
.await
.map(|_| true)

View File

@@ -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,
}
}
}

View File

@@ -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",

View File

@@ -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 远程存储访问",

View File

@@ -8,14 +8,15 @@ type ActionFn<T = any> = () => Promise<T>
export const useAppStore = defineStore('app', () => {
const settings = ref<MergedSettings>({
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<OpenListCoreStatus>({ running: false })

View File

@@ -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 {

View File

@@ -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 () => {
</div>
<div v-if="activeTab === 'rclone'" class="tab-content">
<div class="settings-section">
<h2>{{ t('settings.rclone.api.title') }}</h2>
<p>{{ t('settings.rclone.api.subtitle') }}</p>
<div class="form-grid">
<div class="form-group">
<label>{{ t('settings.rclone.api.port.label') }}</label>
<input
v-model.number="rcloneSettings.api_port"
type="number"
class="form-input"
:placeholder="t('settings.rclone.api.port.placeholder')"
min="1"
max="65535"
/>
<small>{{ t('settings.rclone.api.port.help') }}</small>
</div>
</div>
</div>
<div class="settings-section">
<h2>{{ t('settings.rclone.config.title') }}</h2>
<p>{{ t('settings.rclone.config.subtitle') }}</p>