mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
refactor: 修改为同步请求
This commit is contained in:
@@ -8,6 +8,24 @@ use crate::stream_encoder::encoder_type_to_backend;
|
||||
use crate::video::codec_constraints::{
|
||||
enforce_constraints_with_stream_manager, StreamCodecConstraints,
|
||||
};
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ConfigApplyOptions {
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
impl ConfigApplyOptions {
|
||||
pub const fn forced() -> Self {
|
||||
Self { force: true }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_apply_lock(lock: &Arc<Mutex<()>>, domain: &str) -> Result<OwnedMutexGuard<()>> {
|
||||
lock.clone().try_lock_owned().map_err(|_| {
|
||||
AppError::ServiceUnavailable(format!("{domain} configuration is already applying"))
|
||||
})
|
||||
}
|
||||
|
||||
fn hid_backend_type(config: &HidConfig) -> crate::hid::HidBackendType {
|
||||
match config.backend {
|
||||
@@ -33,8 +51,9 @@ pub async fn apply_video_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &VideoConfig,
|
||||
new_config: &VideoConfig,
|
||||
options: ConfigApplyOptions,
|
||||
) -> Result<()> {
|
||||
if old_config == new_config {
|
||||
if old_config == new_config && !options.force {
|
||||
tracing::info!("Video config unchanged, skipping reload");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -73,10 +92,11 @@ pub async fn apply_stream_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &StreamConfig,
|
||||
new_config: &StreamConfig,
|
||||
options: ConfigApplyOptions,
|
||||
) -> Result<()> {
|
||||
tracing::info!("Applying stream config changes...");
|
||||
|
||||
if old_config.encoder != new_config.encoder {
|
||||
if options.force || old_config.encoder != new_config.encoder {
|
||||
let encoder_backend = encoder_type_to_backend(new_config.encoder.clone());
|
||||
tracing::info!(
|
||||
"Updating encoder backend to: {:?} (from config: {:?})",
|
||||
@@ -86,12 +106,11 @@ pub async fn apply_stream_config(
|
||||
state.webrtc.update_encoder_backend(encoder_backend).await;
|
||||
}
|
||||
|
||||
if old_config.bitrate_preset != new_config.bitrate_preset {
|
||||
if options.force || old_config.bitrate_preset != new_config.bitrate_preset {
|
||||
state
|
||||
.stream_manager
|
||||
.set_bitrate_preset(new_config.bitrate_preset)
|
||||
.await
|
||||
.ok(); // Ignore error if no active stream
|
||||
.await?;
|
||||
}
|
||||
|
||||
let ice_changed = old_config.stun_server != new_config.stun_server
|
||||
@@ -99,7 +118,7 @@ pub async fn apply_stream_config(
|
||||
|| old_config.turn_username != new_config.turn_username
|
||||
|| old_config.turn_password != new_config.turn_password;
|
||||
|
||||
if ice_changed {
|
||||
if options.force || ice_changed {
|
||||
tracing::info!(
|
||||
"Updating ICE config: STUN={:?}, TURN={:?}",
|
||||
new_config.stun_server,
|
||||
@@ -128,6 +147,7 @@ pub async fn apply_hid_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &HidConfig,
|
||||
new_config: &HidConfig,
|
||||
options: ConfigApplyOptions,
|
||||
) -> Result<()> {
|
||||
let current_msd_enabled = state.config.get().msd.enabled;
|
||||
new_config.validate_otg_endpoint_budget(current_msd_enabled)?;
|
||||
@@ -149,6 +169,7 @@ pub async fn apply_hid_config(
|
||||
&& !hid_functions_changed
|
||||
&& !keyboard_leds_changed
|
||||
&& !endpoint_budget_changed
|
||||
&& !options.force
|
||||
{
|
||||
tracing::info!("HID config unchanged, skipping reload");
|
||||
return Ok(());
|
||||
@@ -190,6 +211,7 @@ pub async fn apply_msd_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &MsdConfig,
|
||||
new_config: &MsdConfig,
|
||||
options: ConfigApplyOptions,
|
||||
) -> Result<()> {
|
||||
state
|
||||
.config
|
||||
@@ -222,7 +244,7 @@ pub async fn apply_msd_config(
|
||||
tracing::warn!("Failed to create MSD ventoy directory: {}", e);
|
||||
}
|
||||
|
||||
let needs_reload = old_msd_enabled != new_msd_enabled || msd_dir_changed;
|
||||
let needs_reload = options.force || old_msd_enabled != new_msd_enabled || msd_dir_changed;
|
||||
if !needs_reload {
|
||||
tracing::info!(
|
||||
"MSD enabled state unchanged ({}) and directory unchanged, no reload needed",
|
||||
@@ -272,7 +294,9 @@ pub async fn apply_msd_config(
|
||||
}
|
||||
|
||||
let current_config = state.config.get();
|
||||
if current_config.hid.backend == HidBackend::Otg && old_msd_enabled != new_msd_enabled {
|
||||
if current_config.hid.backend == HidBackend::Otg
|
||||
&& (options.force || old_msd_enabled != new_msd_enabled)
|
||||
{
|
||||
state
|
||||
.hid
|
||||
.reload(crate::hid::HidBackendType::Otg)
|
||||
@@ -306,12 +330,11 @@ pub async fn apply_atx_config(
|
||||
tracing::info!("ATX enabled in config, initializing...");
|
||||
|
||||
let atx = crate::atx::AtxController::new(controller_config);
|
||||
if let Err(e) = atx.init().await {
|
||||
tracing::warn!("ATX initialization failed: {}", e);
|
||||
} else {
|
||||
*state.atx.write().await = Some(atx);
|
||||
tracing::info!("ATX controller initialized successfully");
|
||||
}
|
||||
atx.init()
|
||||
.await
|
||||
.map_err(|e| AppError::Config(format!("ATX initialization failed: {}", e)))?;
|
||||
*state.atx.write().await = Some(atx);
|
||||
tracing::info!("ATX controller initialized successfully");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,25 +354,18 @@ pub async fn apply_audio_config(
|
||||
quality: new_config.quality.parse::<crate::audio::AudioQuality>()?,
|
||||
};
|
||||
|
||||
if let Err(e) = state.audio.update_config(audio_config).await {
|
||||
tracing::error!("Audio config update failed: {}", e);
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Audio config applied: enabled={}, device={}",
|
||||
new_config.enabled,
|
||||
new_config.device
|
||||
);
|
||||
}
|
||||
state.audio.update_config(audio_config).await?;
|
||||
tracing::info!(
|
||||
"Audio config applied: enabled={}, device={}",
|
||||
new_config.enabled,
|
||||
new_config.device
|
||||
);
|
||||
|
||||
if let Err(e) = state
|
||||
state
|
||||
.stream_manager
|
||||
.set_webrtc_audio_enabled(new_config.enabled)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("Failed to update WebRTC audio state: {}", e);
|
||||
} else {
|
||||
tracing::info!("WebRTC audio enabled: {}", new_config.enabled);
|
||||
}
|
||||
.await?;
|
||||
tracing::info!("WebRTC audio enabled: {}", new_config.enabled);
|
||||
|
||||
if new_config.enabled {
|
||||
state.stream_manager.reconnect_webrtc_audio_sources().await;
|
||||
@@ -370,6 +386,7 @@ pub async fn apply_rustdesk_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &crate::rustdesk::config::RustDeskConfig,
|
||||
new_config: &crate::rustdesk::config::RustDeskConfig,
|
||||
options: ConfigApplyOptions,
|
||||
) -> Result<()> {
|
||||
tracing::info!("Applying RustDesk config changes...");
|
||||
|
||||
@@ -378,16 +395,18 @@ pub async fn apply_rustdesk_config(
|
||||
|
||||
if old_config.enabled && !new_config.enabled {
|
||||
if let Some(ref service) = *rustdesk_guard {
|
||||
if let Err(e) = service.stop().await {
|
||||
tracing::error!("Failed to stop RustDesk service: {}", e);
|
||||
}
|
||||
service
|
||||
.stop()
|
||||
.await
|
||||
.map_err(|e| AppError::Config(format!("Failed to stop RustDesk service: {}", e)))?;
|
||||
tracing::info!("RustDesk service stopped");
|
||||
}
|
||||
*rustdesk_guard = None;
|
||||
}
|
||||
|
||||
if new_config.enabled {
|
||||
let need_restart = old_config.rendezvous_server != new_config.rendezvous_server
|
||||
let need_restart = options.force
|
||||
|| old_config.rendezvous_server != new_config.rendezvous_server
|
||||
|| old_config.device_id != new_config.device_id
|
||||
|| old_config.device_password != new_config.device_password;
|
||||
|
||||
@@ -399,24 +418,22 @@ pub async fn apply_rustdesk_config(
|
||||
state.hid.clone(),
|
||||
state.audio.clone(),
|
||||
);
|
||||
if let Err(e) = service.start().await {
|
||||
tracing::error!("Failed to start RustDesk service: {}", e);
|
||||
} else {
|
||||
tracing::info!("RustDesk service started with ID: {}", new_config.device_id);
|
||||
credentials_to_save = service.save_credentials();
|
||||
}
|
||||
service.start().await.map_err(|e| {
|
||||
AppError::Config(format!("Failed to start RustDesk service: {}", e))
|
||||
})?;
|
||||
tracing::info!("RustDesk service started with ID: {}", new_config.device_id);
|
||||
credentials_to_save = service.save_credentials();
|
||||
*rustdesk_guard = Some(std::sync::Arc::new(service));
|
||||
} else if need_restart {
|
||||
if let Some(ref service) = *rustdesk_guard {
|
||||
if let Err(e) = service.restart(new_config.clone()).await {
|
||||
tracing::error!("Failed to restart RustDesk service: {}", e);
|
||||
} else {
|
||||
tracing::info!(
|
||||
"RustDesk service restarted with ID: {}",
|
||||
new_config.device_id
|
||||
);
|
||||
credentials_to_save = service.save_credentials();
|
||||
}
|
||||
service.restart(new_config.clone()).await.map_err(|e| {
|
||||
AppError::Config(format!("Failed to restart RustDesk service: {}", e))
|
||||
})?;
|
||||
tracing::info!(
|
||||
"RustDesk service restarted with ID: {}",
|
||||
new_config.device_id
|
||||
);
|
||||
credentials_to_save = service.save_credentials();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,7 +441,7 @@ pub async fn apply_rustdesk_config(
|
||||
drop(rustdesk_guard);
|
||||
if let Some(updated_config) = credentials_to_save {
|
||||
tracing::info!("Saving RustDesk credentials to config store...");
|
||||
if let Err(e) = state
|
||||
state
|
||||
.config
|
||||
.update(|cfg| {
|
||||
cfg.rustdesk.public_key = updated_config.public_key.clone();
|
||||
@@ -433,12 +450,8 @@ pub async fn apply_rustdesk_config(
|
||||
cfg.rustdesk.signing_private_key = updated_config.signing_private_key.clone();
|
||||
cfg.rustdesk.uuid = updated_config.uuid.clone();
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::warn!("Failed to save RustDesk credentials: {}", e);
|
||||
} else {
|
||||
tracing::info!("RustDesk credentials saved successfully");
|
||||
}
|
||||
.await?;
|
||||
tracing::info!("RustDesk credentials saved successfully");
|
||||
}
|
||||
|
||||
if let Some(message) = enforce_stream_codec_constraints(state).await? {
|
||||
@@ -452,6 +465,7 @@ pub async fn apply_rtsp_config(
|
||||
state: &Arc<AppState>,
|
||||
old_config: &RtspConfig,
|
||||
new_config: &RtspConfig,
|
||||
options: ConfigApplyOptions,
|
||||
) -> Result<()> {
|
||||
tracing::info!("Applying RTSP config changes...");
|
||||
|
||||
@@ -459,15 +473,17 @@ pub async fn apply_rtsp_config(
|
||||
|
||||
if old_config.enabled && !new_config.enabled {
|
||||
if let Some(ref service) = *rtsp_guard {
|
||||
if let Err(e) = service.stop().await {
|
||||
tracing::error!("Failed to stop RTSP service: {}", e);
|
||||
}
|
||||
service
|
||||
.stop()
|
||||
.await
|
||||
.map_err(|e| AppError::Config(format!("Failed to stop RTSP service: {}", e)))?;
|
||||
}
|
||||
*rtsp_guard = None;
|
||||
}
|
||||
|
||||
if new_config.enabled {
|
||||
let need_restart = old_config.bind != new_config.bind
|
||||
let need_restart = options.force
|
||||
|| old_config.bind != new_config.bind
|
||||
|| old_config.port != new_config.port
|
||||
|| old_config.path != new_config.path
|
||||
|| old_config.codec != new_config.codec
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::config::{AtxConfig, HidBackend, HidConfig};
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_atx_config;
|
||||
use super::apply::{apply_atx_config, try_apply_lock};
|
||||
use super::types::AtxConfigUpdate;
|
||||
|
||||
pub async fn get_atx_config(State(state): State<Arc<AppState>>) -> Json<AtxConfig> {
|
||||
@@ -22,6 +22,7 @@ pub async fn update_atx_config(
|
||||
|
||||
req.validate_with_current(&old_atx_config)?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.atx, "atx")?;
|
||||
let mut merged_atx_config = old_atx_config.clone();
|
||||
req.apply_to(&mut merged_atx_config);
|
||||
validate_serial_device_conflict(&merged_atx_config, ¤t_config.hid)?;
|
||||
@@ -35,9 +36,7 @@ pub async fn update_atx_config(
|
||||
|
||||
let new_atx_config = state.config.get().atx.clone();
|
||||
|
||||
if let Err(e) = apply_atx_config(&state, &old_atx_config, &new_atx_config).await {
|
||||
tracing::error!("Failed to apply ATX config: {}", e);
|
||||
}
|
||||
apply_atx_config(&state, &old_atx_config, &new_atx_config).await?;
|
||||
|
||||
Ok(Json(new_atx_config))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::config::AudioConfig;
|
||||
use crate::error::Result;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_audio_config;
|
||||
use super::apply::{apply_audio_config, try_apply_lock};
|
||||
use super::types::AudioConfigUpdate;
|
||||
|
||||
pub async fn get_audio_config(State(state): State<Arc<AppState>>) -> Json<AudioConfig> {
|
||||
@@ -18,6 +18,7 @@ pub async fn update_audio_config(
|
||||
) -> Result<Json<AudioConfig>> {
|
||||
req.validate()?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.audio, "audio")?;
|
||||
let old_audio_config = state.config.get().audio.clone();
|
||||
|
||||
state
|
||||
@@ -29,9 +30,7 @@ pub async fn update_audio_config(
|
||||
|
||||
let new_audio_config = state.config.get().audio.clone();
|
||||
|
||||
if let Err(e) = apply_audio_config(&state, &old_audio_config, &new_audio_config).await {
|
||||
tracing::error!("Failed to apply audio config: {}", e);
|
||||
}
|
||||
apply_audio_config(&state, &old_audio_config, &new_audio_config).await?;
|
||||
|
||||
Ok(Json(new_audio_config))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::config::HidConfig;
|
||||
use crate::error::Result;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_hid_config;
|
||||
use super::apply::{apply_hid_config, try_apply_lock, ConfigApplyOptions};
|
||||
use super::types::HidConfigUpdate;
|
||||
|
||||
pub async fn get_hid_config(State(state): State<Arc<AppState>>) -> Json<HidConfig> {
|
||||
@@ -18,6 +18,7 @@ pub async fn update_hid_config(
|
||||
) -> Result<Json<HidConfig>> {
|
||||
req.validate()?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.otg, "otg")?;
|
||||
let old_hid_config = state.config.get().hid.clone();
|
||||
|
||||
state
|
||||
@@ -29,9 +30,13 @@ pub async fn update_hid_config(
|
||||
|
||||
let new_hid_config = state.config.get().hid.clone();
|
||||
|
||||
if let Err(e) = apply_hid_config(&state, &old_hid_config, &new_hid_config).await {
|
||||
tracing::error!("Failed to apply HID config: {}", e);
|
||||
}
|
||||
apply_hid_config(
|
||||
&state,
|
||||
&old_hid_config,
|
||||
&new_hid_config,
|
||||
ConfigApplyOptions::forced(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(new_hid_config))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::config::MsdConfig;
|
||||
use crate::error::Result;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_msd_config;
|
||||
use super::apply::{apply_msd_config, try_apply_lock, ConfigApplyOptions};
|
||||
use super::types::MsdConfigUpdate;
|
||||
|
||||
pub async fn get_msd_config(State(state): State<Arc<AppState>>) -> Json<MsdConfig> {
|
||||
@@ -18,6 +18,7 @@ pub async fn update_msd_config(
|
||||
) -> Result<Json<MsdConfig>> {
|
||||
req.validate()?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.otg, "otg")?;
|
||||
let old_msd_config = state.config.get().msd.clone();
|
||||
|
||||
state
|
||||
@@ -29,9 +30,13 @@ pub async fn update_msd_config(
|
||||
|
||||
let new_msd_config = state.config.get().msd.clone();
|
||||
|
||||
if let Err(e) = apply_msd_config(&state, &old_msd_config, &new_msd_config).await {
|
||||
tracing::error!("Failed to apply MSD config: {}", e);
|
||||
}
|
||||
apply_msd_config(
|
||||
&state,
|
||||
&old_msd_config,
|
||||
&new_msd_config,
|
||||
ConfigApplyOptions::forced(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(new_msd_config))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use axum::{extract::State, Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::error::Result;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_rtsp_config;
|
||||
use super::apply::{apply_rtsp_config, try_apply_lock, ConfigApplyOptions};
|
||||
use super::types::{RtspConfigResponse, RtspConfigUpdate, RtspStatusResponse};
|
||||
|
||||
pub async fn get_rtsp_config(State(state): State<Arc<AppState>>) -> Json<RtspConfigResponse> {
|
||||
@@ -32,6 +32,7 @@ pub async fn update_rtsp_config(
|
||||
) -> Result<Json<RtspConfigResponse>> {
|
||||
req.validate()?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.rtsp, "rtsp")?;
|
||||
let old_config = state.config.get().rtsp.clone();
|
||||
|
||||
state
|
||||
@@ -42,26 +43,13 @@ pub async fn update_rtsp_config(
|
||||
.await?;
|
||||
|
||||
let new_config = state.config.get().rtsp.clone();
|
||||
if let Err(err) = apply_rtsp_config(&state, &old_config, &new_config).await {
|
||||
tracing::error!("Failed to apply RTSP config: {}", err);
|
||||
if let Err(rollback_err) = state
|
||||
.config
|
||||
.update(|config| {
|
||||
config.rtsp = old_config.clone();
|
||||
})
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to rollback RTSP config after apply failure: {}",
|
||||
rollback_err
|
||||
);
|
||||
return Err(AppError::ServiceUnavailable(format!(
|
||||
"RTSP apply failed: {}; rollback failed: {}",
|
||||
err, rollback_err
|
||||
)));
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
apply_rtsp_config(
|
||||
&state,
|
||||
&old_config,
|
||||
&new_config,
|
||||
ConfigApplyOptions::forced(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(RtspConfigResponse::from(&new_config)))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::error::Result;
|
||||
use crate::rustdesk::config::RustDeskConfig;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_rustdesk_config;
|
||||
use super::apply::{apply_rustdesk_config, try_apply_lock, ConfigApplyOptions};
|
||||
use super::types::RustDeskConfigUpdate;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
@@ -75,6 +75,7 @@ pub async fn update_rustdesk_config(
|
||||
) -> Result<Json<RustDeskConfigResponse>> {
|
||||
req.validate()?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.rustdesk, "rustdesk")?;
|
||||
let old_config = state.config.get().rustdesk.clone();
|
||||
|
||||
state
|
||||
@@ -86,9 +87,13 @@ pub async fn update_rustdesk_config(
|
||||
|
||||
let new_config = state.config.get().rustdesk.clone();
|
||||
|
||||
if let Err(e) = apply_rustdesk_config(&state, &old_config, &new_config).await {
|
||||
tracing::error!("Failed to apply RustDesk config: {}", e);
|
||||
}
|
||||
apply_rustdesk_config(
|
||||
&state,
|
||||
&old_config,
|
||||
&new_config,
|
||||
ConfigApplyOptions::forced(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let constraints = state.stream_manager.codec_constraints().await;
|
||||
if constraints.rustdesk_enabled || constraints.rtsp_enabled {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
use crate::error::Result;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_stream_config;
|
||||
use super::apply::{apply_stream_config, try_apply_lock, ConfigApplyOptions};
|
||||
use super::types::{StreamConfigResponse, StreamConfigUpdate};
|
||||
|
||||
pub async fn get_stream_config(State(state): State<Arc<AppState>>) -> Json<StreamConfigResponse> {
|
||||
@@ -18,6 +18,7 @@ pub async fn update_stream_config(
|
||||
) -> Result<Json<StreamConfigResponse>> {
|
||||
req.validate()?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.stream, "stream")?;
|
||||
let old_stream_config = state.config.get().stream.clone();
|
||||
|
||||
state
|
||||
@@ -29,13 +30,15 @@ pub async fn update_stream_config(
|
||||
|
||||
let new_stream_config = state.config.get().stream.clone();
|
||||
|
||||
if let Err(e) = apply_stream_config(&state, &old_stream_config, &new_stream_config).await {
|
||||
tracing::error!("Failed to apply stream config: {}", e);
|
||||
}
|
||||
apply_stream_config(
|
||||
&state,
|
||||
&old_stream_config,
|
||||
&new_stream_config,
|
||||
ConfigApplyOptions::forced(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Err(e) = super::apply::enforce_stream_codec_constraints(&state).await {
|
||||
tracing::error!("Failed to enforce stream codec constraints: {}", e);
|
||||
}
|
||||
super::apply::enforce_stream_codec_constraints(&state).await?;
|
||||
|
||||
Ok(Json(StreamConfigResponse::from(&new_stream_config)))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::config::VideoConfig;
|
||||
use crate::error::Result;
|
||||
use crate::state::AppState;
|
||||
|
||||
use super::apply::apply_video_config;
|
||||
use super::apply::{apply_video_config, try_apply_lock, ConfigApplyOptions};
|
||||
use super::types::VideoConfigUpdate;
|
||||
|
||||
pub async fn get_video_config(State(state): State<Arc<AppState>>) -> Json<VideoConfig> {
|
||||
@@ -18,6 +18,7 @@ pub async fn update_video_config(
|
||||
) -> Result<Json<VideoConfig>> {
|
||||
req.validate()?;
|
||||
|
||||
let _apply_guard = try_apply_lock(&state.config_apply_locks.video, "video")?;
|
||||
let old_video_config = state.config.get().video.clone();
|
||||
|
||||
state
|
||||
@@ -29,10 +30,13 @@ pub async fn update_video_config(
|
||||
|
||||
let new_video_config = state.config.get().video.clone();
|
||||
|
||||
if let Err(e) = apply_video_config(&state, &old_video_config, &new_video_config).await {
|
||||
tracing::error!("Failed to apply video config: {}", e);
|
||||
// 根据用户选择,仅记录错误,不回滚
|
||||
}
|
||||
apply_video_config(
|
||||
&state,
|
||||
&old_video_config,
|
||||
&new_video_config,
|
||||
ConfigApplyOptions::forced(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(new_video_config))
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use self::config::apply::ConfigApplyOptions;
|
||||
use crate::auth::{Session, SESSION_COOKIE};
|
||||
use crate::config::{AppConfig, StreamMode};
|
||||
use crate::config::StreamMode;
|
||||
use crate::error::{AppError, Result};
|
||||
use crate::state::AppState;
|
||||
use crate::update::{UpdateChannel, UpdateOverviewResponse, UpdateStatusResponse, UpgradeRequest};
|
||||
@@ -739,8 +740,13 @@ pub async fn setup_init(
|
||||
// Start RustDesk if enabled
|
||||
if new_config.rustdesk.enabled {
|
||||
let empty_config = crate::rustdesk::config::RustDeskConfig::default();
|
||||
if let Err(e) =
|
||||
config::apply::apply_rustdesk_config(&state, &empty_config, &new_config.rustdesk).await
|
||||
if let Err(e) = config::apply::apply_rustdesk_config(
|
||||
&state,
|
||||
&empty_config,
|
||||
&new_config.rustdesk,
|
||||
ConfigApplyOptions::default(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("Failed to start RustDesk during setup: {}", e);
|
||||
} else {
|
||||
@@ -751,8 +757,13 @@ pub async fn setup_init(
|
||||
// Start RTSP if enabled
|
||||
if new_config.rtsp.enabled {
|
||||
let empty_config = crate::config::RtspConfig::default();
|
||||
if let Err(e) =
|
||||
config::apply::apply_rtsp_config(&state, &empty_config, &new_config.rtsp).await
|
||||
if let Err(e) = config::apply::apply_rtsp_config(
|
||||
&state,
|
||||
&empty_config,
|
||||
&new_config.rtsp,
|
||||
ConfigApplyOptions::default(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!("Failed to start RTSP during setup: {}", e);
|
||||
} else {
|
||||
@@ -792,160 +803,6 @@ pub async fn setup_init(
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateConfigRequest {
|
||||
#[serde(flatten)]
|
||||
pub updates: serde_json::Value,
|
||||
}
|
||||
|
||||
pub async fn update_config(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<UpdateConfigRequest>,
|
||||
) -> Result<Json<LoginResponse>> {
|
||||
// Keep old config for rollback
|
||||
let old_config = state.config.get();
|
||||
|
||||
tracing::info!("Received config update request");
|
||||
|
||||
// Validate and merge config first (outside the update closure)
|
||||
let config_json = serde_json::to_value(&old_config)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to serialize config: {}", e)))?;
|
||||
|
||||
let merged = merge_json(config_json, req.updates.clone())
|
||||
.map_err(|_| AppError::Internal("Failed to merge config".to_string()))?;
|
||||
|
||||
let new_config: AppConfig = serde_json::from_value(merged)
|
||||
.map_err(|e| AppError::BadRequest(format!("Invalid config format: {}", e)))?;
|
||||
|
||||
let new_config = new_config;
|
||||
new_config
|
||||
.hid
|
||||
.validate_otg_endpoint_budget(new_config.msd.enabled)?;
|
||||
|
||||
// Apply the validated config
|
||||
state.config.set(new_config.clone()).await?;
|
||||
|
||||
tracing::info!("Config updated successfully");
|
||||
|
||||
// Detect which config sections were sent in the request
|
||||
let has_video = req.updates.get("video").is_some();
|
||||
let has_stream = req.updates.get("stream").is_some();
|
||||
let has_hid = req.updates.get("hid").is_some();
|
||||
let has_msd = req.updates.get("msd").is_some();
|
||||
let has_atx = req.updates.get("atx").is_some();
|
||||
let has_audio = req.updates.get("audio").is_some();
|
||||
|
||||
tracing::info!(
|
||||
"Config sections sent: video={}, stream={}, hid={}, msd={}, atx={}, audio={}",
|
||||
has_video,
|
||||
has_stream,
|
||||
has_hid,
|
||||
has_msd,
|
||||
has_atx,
|
||||
has_audio
|
||||
);
|
||||
|
||||
// Get new config for device reloading
|
||||
let new_config = state.config.get();
|
||||
|
||||
if has_video {
|
||||
if let Err(e) =
|
||||
config::apply::apply_video_config(&state, &old_config.video, &new_config.video).await
|
||||
{
|
||||
tracing::error!("Failed to apply video config: {}", e);
|
||||
state.config.set((*old_config).clone()).await?;
|
||||
return Ok(Json(LoginResponse {
|
||||
success: false,
|
||||
message: Some(format!("Video configuration invalid: {}", e)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if has_stream {
|
||||
if let Err(e) =
|
||||
config::apply::apply_stream_config(&state, &old_config.stream, &new_config.stream).await
|
||||
{
|
||||
tracing::error!("Failed to apply stream config: {}", e);
|
||||
state.config.set((*old_config).clone()).await?;
|
||||
return Ok(Json(LoginResponse {
|
||||
success: false,
|
||||
message: Some(format!("Stream configuration invalid: {}", e)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if has_hid {
|
||||
if let Err(e) =
|
||||
config::apply::apply_hid_config(&state, &old_config.hid, &new_config.hid).await
|
||||
{
|
||||
tracing::error!("HID reload failed: {}", e);
|
||||
state.config.set((*old_config).clone()).await?;
|
||||
return Ok(Json(LoginResponse {
|
||||
success: false,
|
||||
message: Some(format!("HID configuration invalid: {}", e)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if has_audio {
|
||||
if let Err(e) =
|
||||
config::apply::apply_audio_config(&state, &old_config.audio, &new_config.audio).await
|
||||
{
|
||||
tracing::warn!("Audio config update failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if has_msd {
|
||||
if let Err(e) =
|
||||
config::apply::apply_msd_config(&state, &old_config.msd, &new_config.msd).await
|
||||
{
|
||||
tracing::error!("MSD initialization failed: {}", e);
|
||||
state.config.set((*old_config).clone()).await?;
|
||||
return Ok(Json(LoginResponse {
|
||||
success: false,
|
||||
message: Some(format!("MSD initialization failed: {}", e)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if has_atx {
|
||||
if let Err(e) =
|
||||
config::apply::apply_atx_config(&state, &old_config.atx, &new_config.atx).await
|
||||
{
|
||||
tracing::error!("ATX configuration invalid: {}", e);
|
||||
state.config.set((*old_config).clone()).await?;
|
||||
return Ok(Json(LoginResponse {
|
||||
success: false,
|
||||
message: Some(format!("ATX configuration invalid: {}", e)),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Json(LoginResponse {
|
||||
success: true,
|
||||
message: Some("Configuration updated".to_string()),
|
||||
}))
|
||||
}
|
||||
|
||||
fn merge_json(
|
||||
base: serde_json::Value,
|
||||
updates: serde_json::Value,
|
||||
) -> std::result::Result<serde_json::Value, ()> {
|
||||
match (base, updates) {
|
||||
(serde_json::Value::Object(mut base), serde_json::Value::Object(updates)) => {
|
||||
for (key, value) in updates {
|
||||
if let Some(base_value) = base.get(&key).cloned() {
|
||||
base.insert(key, merge_json(base_value, value)?);
|
||||
} else {
|
||||
base.insert(key, value);
|
||||
}
|
||||
}
|
||||
Ok(serde_json::Value::Object(base))
|
||||
}
|
||||
(_, updates) => Ok(updates),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DeviceList {
|
||||
pub video: Vec<VideoDevice>,
|
||||
|
||||
@@ -78,7 +78,6 @@ pub fn create_router(state: Arc<AppState>) -> Router {
|
||||
.route("/ws/audio", any(audio_ws_handler))
|
||||
// Configuration management (domain-separated endpoints)
|
||||
.route("/config", get(handlers::config::get_all_config))
|
||||
.route("/config", post(handlers::update_config))
|
||||
.route("/config/video", get(handlers::config::get_video_config))
|
||||
.route(
|
||||
"/config/video",
|
||||
|
||||
Reference in New Issue
Block a user