113 lines
4.2 KiB
Rust
113 lines
4.2 KiB
Rust
//! 订阅(profile)管理:导入 / 更新 / 删除 / 激活。
|
|
|
|
use chrono::Local;
|
|
use std::fs;
|
|
|
|
use super::{MihomoManager, ProfileMeta};
|
|
|
|
impl MihomoManager {
|
|
// ---------- 订阅管理 ----------
|
|
pub async fn import_profile(&self, url: &str, name: &str) -> Result<ProfileMeta, String> {
|
|
// 先读取当前 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<ProfileMeta, String> {
|
|
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()
|
|
}
|
|
}
|