mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-07-29 17:31:45 +08:00
refactor: 删除部分多余的代码和注释
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
mod persistence;
|
||||
mod schema;
|
||||
mod store;
|
||||
|
||||
pub use persistence::ConfigChange;
|
||||
pub use schema::*;
|
||||
pub use store::ConfigStore;
|
||||
|
||||
5
src/config/persistence.rs
Normal file
5
src/config/persistence.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
/// Configuration change event
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigChange {
|
||||
pub key: String,
|
||||
}
|
||||
@@ -1,12 +1,85 @@
|
||||
use crate::video::encoder::BitratePreset;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
// Re-export ExtensionsConfig from extensions module
|
||||
// Re-export domain config types that are embedded in AppConfig.
|
||||
// These are simple data types defined in their respective modules;
|
||||
// keeping the re-export here is acceptable since they flow inward.
|
||||
pub use crate::extensions::ExtensionsConfig;
|
||||
// Re-export RustDeskConfig from rustdesk module
|
||||
pub use crate::rustdesk::config::RustDeskConfig;
|
||||
|
||||
/// Bitrate preset for video encoding
|
||||
///
|
||||
/// Simplifies bitrate configuration by providing three intuitive presets
|
||||
/// plus a custom option for advanced users.
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "value")]
|
||||
#[derive(Default)]
|
||||
pub enum BitratePreset {
|
||||
/// Speed priority: 1 Mbps, lowest latency, smaller GOP
|
||||
Speed,
|
||||
/// Balanced: 4 Mbps, good quality/latency tradeoff
|
||||
#[default]
|
||||
Balanced,
|
||||
/// Quality priority: 8 Mbps, best visual quality
|
||||
Quality,
|
||||
/// Custom bitrate in kbps (for advanced users)
|
||||
Custom(u32),
|
||||
}
|
||||
|
||||
impl BitratePreset {
|
||||
/// Get bitrate value in kbps
|
||||
pub fn bitrate_kbps(&self) -> u32 {
|
||||
match self {
|
||||
Self::Speed => 1000,
|
||||
Self::Balanced => 4000,
|
||||
Self::Quality => 8000,
|
||||
Self::Custom(kbps) => *kbps,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get recommended GOP size based on preset
|
||||
pub fn gop_size(&self, fps: u32) -> u32 {
|
||||
match self {
|
||||
Self::Speed => (fps / 2).max(15),
|
||||
Self::Balanced => fps,
|
||||
Self::Quality => fps * 2,
|
||||
Self::Custom(_) => fps,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get quality preset name for encoder configuration
|
||||
pub fn quality_level(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Speed => "low",
|
||||
Self::Balanced => "medium",
|
||||
Self::Quality => "high",
|
||||
Self::Custom(_) => "medium",
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from kbps value, mapping to nearest preset or Custom
|
||||
pub fn from_kbps(kbps: u32) -> Self {
|
||||
match kbps {
|
||||
0..=1500 => Self::Speed,
|
||||
1501..=6000 => Self::Balanced,
|
||||
6001..=10000 => Self::Quality,
|
||||
_ => Self::Custom(kbps),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BitratePreset {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Speed => write!(f, "Speed (1 Mbps)"),
|
||||
Self::Balanced => write!(f, "Balanced (4 Mbps)"),
|
||||
Self::Quality => write!(f, "Quality (8 Mbps)"),
|
||||
Self::Custom(kbps) => write!(f, "Custom ({} kbps)", kbps),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Main application configuration
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -179,27 +252,13 @@ pub enum OtgEndpointBudget {
|
||||
}
|
||||
|
||||
impl OtgEndpointBudget {
|
||||
pub fn default_for_udc_name(udc: Option<&str>) -> Self {
|
||||
if udc.is_some_and(crate::otg::configfs::is_low_endpoint_udc) {
|
||||
Self::Five
|
||||
} else {
|
||||
Self::Six
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolved(self, udc: Option<&str>) -> Self {
|
||||
/// Resolve endpoint limit assuming a known budget variant (not Auto).
|
||||
pub fn endpoint_limit_raw(&self) -> Option<u8> {
|
||||
match self {
|
||||
Self::Auto => Self::default_for_udc_name(udc),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn endpoint_limit(self, udc: Option<&str>) -> Option<u8> {
|
||||
match self.resolved(udc) {
|
||||
Self::Five => Some(5),
|
||||
Self::Six => Some(6),
|
||||
Self::Unlimited => None,
|
||||
Self::Auto => unreachable!("auto budget must be resolved before use"),
|
||||
Self::Auto => None, // resolved via `HidConfig::resolved_otg_endpoint_limit`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,32 +415,23 @@ impl Default for HidConfig {
|
||||
}
|
||||
|
||||
impl HidConfig {
|
||||
/// Resolve effective OTG HID functions from profile + custom selection.
|
||||
/// Pure logic, no external dependency.
|
||||
pub fn effective_otg_functions(&self) -> OtgHidFunctions {
|
||||
self.otg_profile.resolve_functions(&self.otg_functions)
|
||||
}
|
||||
|
||||
pub fn resolved_otg_udc(&self) -> Option<String> {
|
||||
crate::otg::configfs::resolve_udc_name(self.otg_udc.as_deref())
|
||||
}
|
||||
|
||||
pub fn resolved_otg_endpoint_budget(&self) -> OtgEndpointBudget {
|
||||
self.otg_endpoint_budget
|
||||
.resolved(self.resolved_otg_udc().as_deref())
|
||||
}
|
||||
|
||||
pub fn resolved_otg_endpoint_limit(&self) -> Option<u8> {
|
||||
self.otg_endpoint_budget
|
||||
.endpoint_limit(self.resolved_otg_udc().as_deref())
|
||||
}
|
||||
|
||||
/// Whether keyboard LED feedback is effectively enabled.
|
||||
pub fn effective_otg_keyboard_leds(&self) -> bool {
|
||||
self.otg_keyboard_leds && self.effective_otg_functions().keyboard
|
||||
}
|
||||
|
||||
/// Effective HID functions after applying all constraints.
|
||||
pub fn constrained_otg_functions(&self) -> OtgHidFunctions {
|
||||
self.effective_otg_functions()
|
||||
}
|
||||
|
||||
/// Calculate required endpoint count for the current function selection.
|
||||
pub fn effective_otg_required_endpoints(&self, msd_enabled: bool) -> u8 {
|
||||
let functions = self.effective_otg_functions();
|
||||
let mut endpoints = functions.endpoint_cost(self.effective_otg_keyboard_leds());
|
||||
@@ -391,6 +441,7 @@ impl HidConfig {
|
||||
endpoints
|
||||
}
|
||||
|
||||
/// Validate endpoint budget for the current OTG configuration (UDC-aware when budget is Auto).
|
||||
pub fn validate_otg_endpoint_budget(&self, msd_enabled: bool) -> crate::error::Result<()> {
|
||||
if self.backend != HidBackend::Otg {
|
||||
return Ok(());
|
||||
@@ -403,8 +454,9 @@ impl HidConfig {
|
||||
));
|
||||
}
|
||||
|
||||
let resolved_limit = self.resolved_otg_endpoint_limit();
|
||||
let required = self.effective_otg_required_endpoints(msd_enabled);
|
||||
if let Some(limit) = self.resolved_otg_endpoint_limit() {
|
||||
if let Some(limit) = resolved_limit {
|
||||
if required > limit {
|
||||
return Err(crate::error::AppError::BadRequest(format!(
|
||||
"OTG selection requires {} endpoints, but the configured limit is {}",
|
||||
@@ -415,6 +467,40 @@ impl HidConfig {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Effective OTG UDC name (for change detection / service).
|
||||
#[inline]
|
||||
pub fn resolved_otg_udc(&self) -> Option<String> {
|
||||
if self.backend != HidBackend::Otg {
|
||||
return None;
|
||||
}
|
||||
self.otg_udc
|
||||
.as_ref()
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.or_else(|| crate::otg::OtgGadgetManager::find_udc())
|
||||
}
|
||||
|
||||
/// Resolved endpoint limit used for OTG gadget allocator / validation.
|
||||
#[inline]
|
||||
pub fn resolved_otg_endpoint_limit(&self) -> Option<u8> {
|
||||
if self.backend != HidBackend::Otg {
|
||||
return None;
|
||||
}
|
||||
match self.otg_endpoint_budget {
|
||||
OtgEndpointBudget::Five => Some(5),
|
||||
OtgEndpointBudget::Six => Some(6),
|
||||
OtgEndpointBudget::Unlimited => None,
|
||||
OtgEndpointBudget::Auto => {
|
||||
let udc = self.resolved_otg_udc().unwrap_or_default();
|
||||
if crate::otg::configfs::is_low_endpoint_udc(&udc) {
|
||||
Some(5)
|
||||
} else {
|
||||
Some(6)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// MSD configuration
|
||||
@@ -511,7 +597,7 @@ impl Default for AudioConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
device: "default".to_string(),
|
||||
device: String::new(),
|
||||
quality: "balanced".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -606,21 +692,6 @@ pub enum EncoderType {
|
||||
}
|
||||
|
||||
impl EncoderType {
|
||||
/// Convert to EncoderBackend for registry queries
|
||||
pub fn to_backend(&self) -> Option<crate::video::encoder::registry::EncoderBackend> {
|
||||
use crate::video::encoder::registry::EncoderBackend;
|
||||
match self {
|
||||
EncoderType::Auto => None,
|
||||
EncoderType::Software => Some(EncoderBackend::Software),
|
||||
EncoderType::Vaapi => Some(EncoderBackend::Vaapi),
|
||||
EncoderType::Nvenc => Some(EncoderBackend::Nvenc),
|
||||
EncoderType::Qsv => Some(EncoderBackend::Qsv),
|
||||
EncoderType::Amf => Some(EncoderBackend::Amf),
|
||||
EncoderType::Rkmpp => Some(EncoderBackend::Rkmpp),
|
||||
EncoderType::V4l2m2m => Some(EncoderBackend::V4l2m2m),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get display name for UI
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
@@ -687,19 +758,17 @@ impl Default for StreamConfig {
|
||||
}
|
||||
|
||||
impl StreamConfig {
|
||||
/// Check if using public ICE servers (user left fields empty)
|
||||
/// Whether built-in / public ICE is used (no custom STUN or TURN URL configured).
|
||||
pub fn is_using_public_ice_servers(&self) -> bool {
|
||||
use crate::webrtc::config::public_ice;
|
||||
self.stun_server
|
||||
let no_custom_stun = self
|
||||
.stun_server
|
||||
.as_ref()
|
||||
.map(|s| s.is_empty())
|
||||
.unwrap_or(true)
|
||||
&& self
|
||||
.turn_server
|
||||
.as_ref()
|
||||
.map(|s| s.is_empty())
|
||||
.unwrap_or(true)
|
||||
&& public_ice::is_configured()
|
||||
.map_or(true, |s| s.trim().is_empty());
|
||||
let no_custom_turn = self
|
||||
.turn_server
|
||||
.as_ref()
|
||||
.map_or(true, |s| s.trim().is_empty());
|
||||
no_custom_stun && no_custom_turn
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use arc_swap::ArcSwap;
|
||||
use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite};
|
||||
use std::path::Path;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::persistence::ConfigChange;
|
||||
use super::AppConfig;
|
||||
use crate::error::{AppError, Result};
|
||||
|
||||
@@ -23,127 +22,23 @@ pub struct ConfigStore {
|
||||
write_lock: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
/// Configuration change event
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigChange {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
impl ConfigStore {
|
||||
/// Create a new configuration store
|
||||
pub async fn new(db_path: &Path) -> Result<Self> {
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = db_path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
let db_url = format!("sqlite:{}?mode=rwc", db_path.display());
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
// SQLite uses single-writer mode, 2 connections is sufficient for embedded devices
|
||||
// One for reads, one for writes to avoid blocking
|
||||
.max_connections(2)
|
||||
// Set reasonable timeouts for embedded environments
|
||||
.acquire_timeout(Duration::from_secs(5))
|
||||
.idle_timeout(Duration::from_secs(300))
|
||||
.connect(&db_url)
|
||||
.await?;
|
||||
|
||||
// Initialize database schema
|
||||
Self::init_schema(&pool).await?;
|
||||
|
||||
// Load or create default config
|
||||
let config = Self::load_config(&pool).await?;
|
||||
let cache = Arc::new(ArcSwap::from_pointee(config));
|
||||
|
||||
let (change_tx, _) = broadcast::channel(16);
|
||||
|
||||
pub fn new(pool: Pool<Sqlite>) -> Result<Self> {
|
||||
// Load or create default config synchronously wrapper
|
||||
// (actual DB load is async, handled in init())
|
||||
Ok(Self {
|
||||
pool,
|
||||
cache,
|
||||
change_tx,
|
||||
cache: Arc::new(ArcSwap::from_pointee(AppConfig::default())),
|
||||
change_tx: broadcast::channel(16).0,
|
||||
write_lock: Arc::new(Mutex::new(())),
|
||||
})
|
||||
}
|
||||
|
||||
/// Initialize database schema
|
||||
async fn init_schema(pool: &Pool<Sqlite>) -> Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
expires_at TEXT NOT NULL,
|
||||
data TEXT
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS api_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
token_hash TEXT NOT NULL,
|
||||
permissions TEXT NOT NULL,
|
||||
expires_at TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_used TEXT
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS wol_history (
|
||||
mac_address TEXT PRIMARY KEY,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE INDEX IF NOT EXISTS idx_wol_history_updated_at
|
||||
ON wol_history(updated_at DESC)
|
||||
"#,
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
/// Load configuration from database (call after new())
|
||||
pub async fn load(&self) -> Result<()> {
|
||||
let config = Self::load_config(&self.pool).await?;
|
||||
self.cache.store(Arc::new(config));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -244,16 +139,12 @@ impl ConfigStore {
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
self.cache.load().initialized
|
||||
}
|
||||
|
||||
/// Get database pool for session management
|
||||
pub fn pool(&self) -> &Pool<Sqlite> {
|
||||
&self.pool
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::DatabasePool;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -261,7 +152,11 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
let db_path = dir.path().join("test.db");
|
||||
|
||||
let store = ConfigStore::new(&db_path).await.unwrap();
|
||||
let db = DatabasePool::new(&db_path).await.unwrap();
|
||||
db.init_schema().await.unwrap();
|
||||
|
||||
let store = ConfigStore::new(db.clone_pool()).unwrap();
|
||||
store.load().await.unwrap();
|
||||
|
||||
// Check default config (now lock-free, no await needed)
|
||||
let config = store.get();
|
||||
@@ -282,7 +177,8 @@ mod tests {
|
||||
assert_eq!(config.web.http_port, 9000);
|
||||
|
||||
// Create new store instance and verify persistence
|
||||
let store2 = ConfigStore::new(&db_path).await.unwrap();
|
||||
let store2 = ConfigStore::new(db.clone_pool()).unwrap();
|
||||
store2.load().await.unwrap();
|
||||
let config = store2.get();
|
||||
assert!(config.initialized);
|
||||
assert_eq!(config.web.http_port, 9000);
|
||||
|
||||
Reference in New Issue
Block a user