use anyhow::Context;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub server: ServerConfig,
#[serde(default)]
pub auth: AuthConfig,
#[serde(default)]
pub service: ServiceConfig,
#[serde(default)]
pub clipboard: ClipboardConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
#[serde(default = "default_bind")]
pub bind: String,
#[serde(default = "default_port")]
pub port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
#[serde(default)]
pub pin: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClipboardConfig {
#[serde(default = "default_max_fetch_size")]
pub max_fetch_size: usize,
}
fn default_max_fetch_size() -> usize {
524288 }
impl Default for ClipboardConfig {
fn default() -> Self {
Self {
max_fetch_size: default_max_fetch_size(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceConfig {
#[serde(default = "default_service_name")]
pub name: String,
}
fn default_bind() -> String {
"0.0.0.0".into()
}
fn default_port() -> u16 {
9527
}
fn default_service_name() -> String {
hostname::get()
.ok()
.and_then(|h| h.into_string().ok())
.unwrap_or_else(|| "ClipLink".into())
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
bind: default_bind(),
port: default_port(),
}
}
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
pin: String::new(),
}
}
}
impl Default for ServiceConfig {
fn default() -> Self {
Self {
name: default_service_name(),
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
server: ServerConfig::default(),
auth: AuthConfig::default(),
service: ServiceConfig::default(),
clipboard: ClipboardConfig::default(),
}
}
}
impl Config {
fn config_dir() -> PathBuf {
#[cfg(target_os = "windows")]
{
dirs::config_dir()
.unwrap_or_default()
.join("cliplinkd")
}
#[cfg(not(target_os = "windows"))]
{
dirs::home_dir()
.unwrap_or_default()
.join(".config")
.join("cliplinkd")
}
}
pub fn load() -> anyhow::Result<Self> {
let paths: Vec<PathBuf> = vec![
Self::config_dir().join("cliplinkd.toml"),
PathBuf::from("cliplinkd.toml"),
];
for path in &paths {
if path.exists() {
let content = std::fs::read_to_string(path).with_context(|| {
format!("Failed to read config from {}", path.display())
})?;
let config: Config = toml::from_str(&content).with_context(|| {
format!("Failed to parse config from {}", path.display())
})?;
tracing::info!("Loaded config from {}", path.display());
return Ok(config);
}
}
tracing::info!("No config file found, using defaults");
Ok(Config::default())
}
pub fn save(&self) -> anyhow::Result<PathBuf> {
let dir = Self::config_dir();
std::fs::create_dir_all(&dir)?;
let path = dir.join("cliplinkd.toml");
let content = toml::to_string_pretty(self)?;
std::fs::write(&path, content)?;
Ok(path)
}
}