//! 订阅(profile)管理:导入 / 更新 / 删除 / 激活。 use chrono::Local; use std::fs; use super::{MihomoManager, ProfileMeta}; impl MihomoManager { // ---------- 订阅管理 ---------- pub async fn import_profile(&self, url: &str, name: &str) -> Result { // 先读取当前 settings(此时新 profile 文件还未写入,reconcile 不会误添加) let mut settings = self.load_settings(); let resp = self .client .get(url) .header("User-Agent", "clash.meta/thing") .timeout(std::time::Duration::from_secs(30)) .send() .await .map_err(|e| format!("下载订阅失败: {}", e))?; if !resp.status().is_success() { return Err(format!("订阅下载失败: HTTP {}", resp.status())); } let content = resp.text().await.map_err(|e| e.to_string())?; if !content.contains("proxies") && !content.contains("Proxy") { return Err("订阅内容不像有效的 Clash/mihomo 配置".into()); } let id = format!("profile-{}", Local::now().format("%Y%m%d%H%M%S")); let path = self.profiles_dir().join(format!("{}.yaml", id)); fs::write(&path, &content).map_err(|e| e.to_string())?; let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); let meta = ProfileMeta { id: id.clone(), name: name.to_string(), url: url.to_string(), added_at: now.clone(), updated_at: now, size: content.len() as u64, }; // 去重保护:避免 reconcile 已添加同 id(理论上不会,因为文件刚写入) if !settings.profiles.iter().any(|p| p.id == id) { settings.profiles.push(meta.clone()); } if settings.current_profile.is_none() { settings.current_profile = Some(id); } self.save_settings(&settings)?; Ok(meta) } pub async fn update_profile(&self, id: &str) -> Result { let mut settings = self.load_settings(); let meta = settings .profiles .iter() .find(|p| p.id == id) .cloned() .ok_or_else(|| "订阅不存在".to_string())?; let resp = self .client .get(&meta.url) .header("User-Agent", "clash.meta/thing") .timeout(std::time::Duration::from_secs(30)) .send() .await .map_err(|e| format!("更新订阅失败: {}", e))?; if !resp.status().is_success() { return Err(format!("更新订阅失败: HTTP {}", resp.status())); } let content = resp.text().await.map_err(|e| e.to_string())?; let path = self.profiles_dir().join(format!("{}.yaml", id)); fs::write(&path, &content).map_err(|e| e.to_string())?; let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); let size = content.len() as u64; if let Some(p) = settings.profiles.iter_mut().find(|p| p.id == id) { p.updated_at = now.clone(); p.size = size; } self.save_settings(&settings)?; Ok(ProfileMeta { id: id.to_string(), name: meta.name, url: meta.url, added_at: meta.added_at, updated_at: now, size, }) } pub fn delete_profile(&self, id: &str) -> Result<(), String> { let path = self.profiles_dir().join(format!("{}.yaml", id)); fs::remove_file(&path).ok(); let mut settings = self.load_settings(); settings.profiles.retain(|p| p.id != id); if settings.current_profile.as_deref() == Some(id) { settings.current_profile = settings.profiles.first().map(|p| p.id.clone()); } self.save_settings(&settings)?; Ok(()) } pub fn activate_profile(&self, id: &str) -> Result<(), String> { let mut settings = self.load_settings(); if !settings.profiles.iter().any(|p| p.id == id) { return Err("订阅不存在".into()); } settings.current_profile = Some(id.to_string()); self.save_settings(&settings)?; self.generate_config() } }