use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::engine::models::Server;
use crate::engine::providers::ProviderKind;
use crate::engine::{EngineConfig, PingConfig, TransferConfig};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct Settings {
pub theme: String,
pub refresh_rate: u16,
pub provider: ProviderKind,
pub animation_speed: f64,
pub engine: EngineSection,
#[serde(skip_serializing_if = "Option::is_none")]
pub repeat_interval: Option<String>,
pub notify: bool,
pub servers: Vec<ServerEntry>,
pub wallpaper: WallpaperSection,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default, deny_unknown_fields)]
pub struct WallpaperSection {
pub kind: WallpaperKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum WallpaperKind {
#[default]
None,
Gradient,
}
impl Default for Settings {
fn default() -> Self {
Self {
theme: "default".to_owned(),
refresh_rate: 30,
provider: ProviderKind::default(),
animation_speed: 1.0,
engine: EngineSection::default(),
repeat_interval: None,
notify: true,
servers: Vec::new(),
wallpaper: WallpaperSection::default(),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct EngineSection {
pub ping_samples: u32,
pub ping_interval_ms: u64,
pub duration_secs: u64,
pub connections: usize,
pub timeout_secs: u64,
pub upload_chunk_kb: usize,
}
impl Default for EngineSection {
fn default() -> Self {
let ping = PingConfig::default();
let transfer = TransferConfig::default();
Self {
ping_samples: ping.samples,
ping_interval_ms: ping.interval.as_millis() as u64,
duration_secs: transfer.duration.as_secs(),
connections: transfer.connections,
timeout_secs: transfer.connect_timeout.as_secs(),
upload_chunk_kb: transfer.upload_chunk_bytes / 1024,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ServerEntry {
pub name: String,
pub url: String,
#[serde(default = "default_download_path")]
pub download_path: String,
#[serde(default = "default_upload_path")]
pub upload_path: String,
#[serde(default = "default_ping_path")]
pub ping_path: String,
}
fn default_download_path() -> String {
"garbage.php?ckSize=100".to_owned()
}
fn default_upload_path() -> String {
"empty.php".to_owned()
}
fn default_ping_path() -> String {
"empty.php".to_owned()
}
impl Settings {
#[must_use]
pub fn tick_rate(&self) -> Duration {
let fps = u64::from(self.refresh_rate.clamp(1, 60));
Duration::from_millis(1000 / fps)
}
#[must_use]
pub fn animation_speed(&self) -> f64 {
if self.animation_speed.is_finite() {
self.animation_speed.clamp(0.1, 5.0)
} else {
1.0
}
}
#[must_use]
pub fn engine_config(&self) -> EngineConfig {
let defaults = EngineConfig::default();
EngineConfig {
ping: PingConfig {
samples: self.engine.ping_samples.clamp(3, 100),
interval: Duration::from_millis(self.engine.ping_interval_ms.clamp(10, 2000)),
timeout: defaults.ping.timeout,
},
transfer: TransferConfig {
duration: Duration::from_secs(self.engine.duration_secs.clamp(3, 60)),
connections: self.engine.connections.clamp(1, 16),
sample_interval: defaults.transfer.sample_interval,
ema_alpha: defaults.transfer.ema_alpha,
upload_chunk_bytes: self.engine.upload_chunk_kb.clamp(64, 8192) * 1024,
connect_timeout: Duration::from_secs(self.engine.timeout_secs.clamp(2, 120)),
lead_in: defaults.transfer.lead_in,
},
ip_family: None,
}
}
#[must_use]
pub fn repeat_interval(&self) -> Option<Duration> {
self.repeat_interval
.as_deref()
.and_then(|value| crate::utils::duration::parse_interval(value).ok())
}
pub fn apply_overrides(&mut self, duration_secs: Option<u64>, connections: Option<usize>) {
if let Some(duration) = duration_secs {
self.engine.duration_secs = duration;
}
if let Some(connections) = connections {
self.engine.connections = connections;
}
}
#[must_use]
pub fn custom_servers(&self) -> Vec<Server> {
self.servers
.iter()
.filter(|entry| !entry.name.trim().is_empty() && !entry.url.trim().is_empty())
.map(|entry| {
Server::from_base(
entry.name.trim(),
&entry.url,
&entry.download_path,
&entry.upload_path,
&entry.ping_path,
)
})
.collect()
}
}