use crate::error::Result;
use std::path::Path;
pub struct ConfigUtils;
impl ConfigUtils {
pub fn generate_default_config() -> String {
let config = super::ConfigFile::new();
serde_yaml::to_string(&config).unwrap_or_default()
}
pub fn validate_config_syntax(content: &str) -> Result<()> {
let _: super::ConfigFile = serde_yaml::from_str(content)
.map_err(|e| crate::error::ConfigError::ParseFailed(e.to_string()))?;
Ok(())
}
pub fn find_config_files() -> Vec<std::path::PathBuf> {
let mut config_files = Vec::new();
if let Some(config_dir) = dirs::config_dir() {
let standard_path = config_dir.join("disksync").join("config.yaml");
if standard_path.exists() {
config_files.push(standard_path);
}
}
let current_dir = std::env::current_dir().unwrap_or_default();
let local_config = current_dir.join("disksync.yaml");
if local_config.exists() {
config_files.push(local_config);
}
if let Ok(env_path) = std::env::var("DISKSYNC_CONFIG") {
let path = Path::new(&env_path);
if path.exists() {
config_files.push(path.to_path_buf());
}
}
config_files
}
pub fn merge_configs(configs: &[super::ConfigFile]) -> super::ConfigFile {
let mut merged = super::ConfigFile::new();
for config in configs {
for account in &config.accounts {
if !merged.accounts.iter().any(|a| a.id == account.id) {
merged.accounts.push(account.clone());
}
}
for task in &config.tasks {
if !merged.tasks.iter().any(|t| t.id == task.id) {
merged.tasks.push(task.clone());
}
}
merged.global_settings = config.global_settings.clone();
merged.encryption_keys = config.encryption_keys.clone();
merged.plugins = config.plugins.clone();
merged.schedules = config.schedules.clone();
merged.network_settings = config.network_settings.clone();
merged.security_settings = config.security_settings.clone();
}
merged
}
pub fn load_from_env() -> super::ConfigFile {
let mut config = super::ConfigFile::new();
if let Ok(log_level) = std::env::var("DISKSYNC_LOG_LEVEL") {
config.global_settings.log_level = match log_level.to_lowercase().as_str() {
"off" => super::LogLevel::Off,
"error" => super::LogLevel::Error,
"warn" => super::LogLevel::Warn,
"info" => super::LogLevel::Info,
"debug" => super::LogLevel::Debug,
"trace" => super::LogLevel::Trace,
_ => super::LogLevel::Info,
};
}
if let Ok(data_dir) = std::env::var("DISKSYNC_DATA_DIR") {
config.global_settings.data_dir = Some(data_dir.into());
}
if let Ok(proxy) = std::env::var("DISKSYNC_PROXY") {
config
.network_settings
.get_or_insert_with(|| super::NetworkSettings::default())
.proxy = Some(super::ProxyConfig {
url: proxy,
username: std::env::var("DISKSYNC_PROXY_USER").ok(),
password: std::env::var("DISKSYNC_PROXY_PASS").ok(),
bypass_for_local: true,
bypass_list: Vec::new(),
});
}
config
}
pub fn generate_template(template_type: ConfigTemplate) -> String {
match template_type {
ConfigTemplate::Full => Self::generate_default_config(),
ConfigTemplate::Minimal => {
let mut config = super::ConfigFile::new();
config.accounts.clear();
config.tasks.clear();
config.encryption_keys.clear();
config.plugins.clear();
config.schedules.clear();
serde_yaml::to_string(&config).unwrap_or_default()
}
ConfigTemplate::AliYunDrive => {
let config = r#"
accounts:
- id: "aliyun_example"
name: "阿里云盘示例"
provider: AliYunDrive
credentials:
refresh_token: "your_refresh_token_here"
rate_limit:
requests_per_minute: 60
max_concurrent: 5
chunk_size: 4194304
tasks:
- id: "backup_photos"
name: "照片备份"
source_account: "aliyun_example"
source_path: "/相册"
target_account: "local_backup"
target_path: "/backup/photos"
schedule:
Cron: "0 2 * * *"
filters:
- Include: "*.jpg"
- Include: "*.png"
- Exclude: "thumbnails/*"
encryption:
algorithm: Aes256Gcm
key_id: "master_key"
iv_mode: Random
diff_mode: Smart
preserve_metadata: true
encryption_keys:
- id: "master_key"
name: "主密钥"
algorithm: Aes256Gcm
key_data:
Local:
encrypted_data: "base64_encrypted_key_here"
salt: "base64_salt_here"
created_at: "2024-01-01T00:00:00Z"
"#;
config.to_string()
}
ConfigTemplate::WebDAV => {
let config = r#"
accounts:
- id: "webdav_example"
name: "WebDAV服务器"
provider: WebDAV
credentials:
url: "https://dav.example.com"
username: "your_username"
password: "your_password"
rate_limit:
requests_per_minute: 30
max_concurrent: 3
chunk_size: 2097152
"#;
config.to_string()
}
}
}
pub fn diff_configs(config1: &super::ConfigFile, config2: &super::ConfigFile) -> ConfigDiff {
let mut diff = ConfigDiff::new();
for account in &config1.accounts {
if !config2.accounts.iter().any(|a| a.id == account.id) {
diff.accounts_added.push(account.clone());
}
}
for account in &config2.accounts {
if !config1.accounts.iter().any(|a| a.id == account.id) {
diff.accounts_removed.push(account.clone());
}
}
for task in &config1.tasks {
if !config2.tasks.iter().any(|t| t.id == task.id) {
diff.tasks_added.push(task.clone());
}
}
for task in &config2.tasks {
if !config1.tasks.iter().any(|t| t.id == task.id) {
diff.tasks_removed.push(task.clone());
}
}
if config1.global_settings.log_level != config2.global_settings.log_level {
diff.settings_changed.push("log_level".to_string());
}
diff
}
}
pub enum ConfigTemplate {
Full,
Minimal,
AliYunDrive,
WebDAV,
}
#[derive(Debug)]
pub struct ConfigDiff {
pub accounts_added: Vec<super::AccountConfig>,
pub accounts_removed: Vec<super::AccountConfig>,
pub tasks_added: Vec<super::SyncTask>,
pub tasks_removed: Vec<super::SyncTask>,
pub settings_changed: Vec<String>,
}
impl ConfigDiff {
pub fn new() -> Self {
Self {
accounts_added: Vec::new(),
accounts_removed: Vec::new(),
tasks_added: Vec::new(),
tasks_removed: Vec::new(),
settings_changed: Vec::new(),
}
}
pub fn has_changes(&self) -> bool {
!self.accounts_added.is_empty()
|| !self.accounts_removed.is_empty()
|| !self.tasks_added.is_empty()
|| !self.tasks_removed.is_empty()
|| !self.settings_changed.is_empty()
}
}